diff options
Diffstat (limited to 'spec')
1015 files changed, 30642 insertions, 9642 deletions
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb index 6d8b9865dcb..fc1bf67d7b9 100644 --- a/spec/bin/changelog_spec.rb +++ b/spec/bin/changelog_spec.rb @@ -84,7 +84,7 @@ describe 'bin/changelog' do expect do expect do expect { described_class.read_type }.to raise_error(SystemExit) - end.to output("Invalid category index, please select an index between 1 and 7\n").to_stderr + end.to output("Invalid category index, please select an index between 1 and 8\n").to_stderr end.to output.to_stdout end end diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb index 1d1070e90f4..e6ba596117a 100644 --- a/spec/controllers/admin/hooks_controller_spec.rb +++ b/spec/controllers/admin/hooks_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::HooksController do post :create, hook: hook_params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(SystemHook.all.size).to eq(1) expect(SystemHook.first).to have_attributes(hook_params) end diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index 8f1f0ba89ff..944680b3f42 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -22,7 +22,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "doesn't sign us in" do @@ -46,7 +46,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "doesn't sign us in as the impersonator" do @@ -65,7 +65,7 @@ describe Admin::ImpersonationsController do it "responds with status 404" do delete :destroy - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "doesn't sign us in as the impersonator" do diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index 373260b3978..d5a3c250f31 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -27,7 +27,7 @@ describe Admin::ProjectsController do get :index - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.body).not_to match(pending_delete_project.name) expect(response.body).to match(project.name) end diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index b5fe40d0510..312dbdd0624 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -11,7 +11,7 @@ describe Admin::RunnersController do it 'lists all runners' do get :index - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -19,13 +19,13 @@ describe Admin::RunnersController do it 'shows a particular runner' do get :show, id: runner.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'shows 404 for unknown runner' do get :show, id: 0 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -39,7 +39,7 @@ describe Admin::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.description).to eq(new_desc) end end @@ -48,7 +48,7 @@ describe Admin::RunnersController do it 'destroys the runner' do delete :destroy, id: runner.id - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(Ci::Runner.find_by(id: runner.id)).to be_nil end end @@ -63,7 +63,7 @@ describe Admin::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.active).to eq(true) end end @@ -78,7 +78,7 @@ describe Admin::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.active).to eq(false) end end diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 249bd948847..701211c2586 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::ServicesController do it 'successfully displays the template' do get :edit, id: service.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -46,7 +46,7 @@ describe Admin::ServicesController do put :update, id: service.id, service: { active: true } - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end it 'does not call the propagation worker when service is not active' do @@ -54,7 +54,7 @@ describe Admin::ServicesController do put :update, id: service.id, service: { properties: {} } - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end end diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb index 585ca31389d..7a96ef6a5cc 100644 --- a/spec/controllers/admin/spam_logs_controller_spec.rb +++ b/spec/controllers/admin/spam_logs_controller_spec.rb @@ -14,7 +14,7 @@ describe Admin::SpamLogsController do it 'lists all spam logs' do get :index - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -22,14 +22,14 @@ describe Admin::SpamLogsController do it 'removes only the spam log when removing log' do expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1) expect(User.find(user.id)).to be_truthy - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'removes user and his spam logs when removing the user' do delete :destroy, id: first_spam.id, remove_user: true expect(flash[:notice]).to eq "User #{user.username} was successfully removed." - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(SpamLog.count).to eq(0) expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) end @@ -42,7 +42,7 @@ describe Admin::SpamLogsController do it 'submits the log as ham' do post :mark_as_ham, id: first_spam.id - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(SpamLog.find(first_spam.id).submitted_as_ham).to be_truthy end end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index aadd3317875..f044a068938 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Admin::UsersController do let(:user) { create(:user) } - let(:admin) { create(:admin) } + set(:admin) { create(:admin) } before do sign_in(admin) @@ -19,7 +19,7 @@ describe Admin::UsersController do it 'deletes user and ghosts their contributions' do delete :destroy, id: user.username, format: :json - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(User.exists?(user.id)).to be_falsy expect(issue.reload.author).to be_ghost end @@ -27,7 +27,7 @@ describe Admin::UsersController do it 'deletes the user and their contributions when hard delete is specified' do delete :destroy, id: user.username, hard_delete: true, format: :json - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(User.exists?(user.id)).to be_falsy expect(Issue.exists?(issue.id)).to be_falsy end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 59a6cfbf4f5..b73ca0c2346 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -50,70 +50,36 @@ describe ApplicationController do end end - describe "#authenticate_user_from_token!" do - describe "authenticating a user from a private token" do - controller(described_class) do - def index - render text: "authenticated" - end - end - - context "when the 'private_token' param is populated with the private token" do - it "logs the user in" do - get :index, private_token: user.private_token - expect(response).to have_http_status(200) - expect(response.body).to eq("authenticated") - end - end - - context "when the 'PRIVATE-TOKEN' header is populated with the private token" do - it "logs the user in" do - @request.headers['PRIVATE-TOKEN'] = user.private_token - get :index - expect(response).to have_http_status(200) - expect(response.body).to eq("authenticated") - end - end - - it "doesn't log the user in otherwise" do - @request.headers['PRIVATE-TOKEN'] = "token" - get :index, private_token: "token", authenticity_token: "token" - expect(response.status).not_to eq(200) - expect(response.body).not_to eq("authenticated") + describe "#authenticate_user_from_personal_access_token!" do + controller(described_class) do + def index + render text: 'authenticated' end end - describe "authenticating a user from a personal access token" do - controller(described_class) do - def index - render text: 'authenticated' - end - end - - let(:personal_access_token) { create(:personal_access_token, user: user) } + let(:personal_access_token) { create(:personal_access_token, user: user) } - context "when the 'personal_access_token' param is populated with the personal access token" do - it "logs the user in" do - get :index, private_token: personal_access_token.token - expect(response).to have_http_status(200) - expect(response.body).to eq('authenticated') - end + context "when the 'personal_access_token' param is populated with the personal access token" do + it "logs the user in" do + get :index, private_token: personal_access_token.token + expect(response).to have_gitlab_http_status(200) + expect(response.body).to eq('authenticated') end + end - context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do - it "logs the user in" do - @request.headers["PRIVATE-TOKEN"] = personal_access_token.token - get :index - expect(response).to have_http_status(200) - expect(response.body).to eq('authenticated') - end + context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do + it "logs the user in" do + @request.headers["PRIVATE-TOKEN"] = personal_access_token.token + get :index + expect(response).to have_gitlab_http_status(200) + expect(response.body).to eq('authenticated') end + end - it "doesn't log the user in otherwise" do - get :index, private_token: "token" - expect(response.status).not_to eq(200) - expect(response.body).not_to eq('authenticated') - end + it "doesn't log the user in otherwise" do + get :index, private_token: "token" + expect(response.status).not_to eq(200) + expect(response.body).not_to eq('authenticated') end end @@ -152,21 +118,25 @@ describe ApplicationController do end end + before do + sign_in user + 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 + get :index, format: requested_format - expect(response).to have_http_status 200 + expect(response).to have_gitlab_http_status 200 end end context 'when format is not handled' do it 'returns 404 response' do - get :index, private_token: user.private_token + get :index - expect(response).to have_http_status 404 + expect(response).to have_gitlab_http_status 404 end end end @@ -183,7 +153,7 @@ describe ApplicationController do context 'when the request format is atom' do it "logs the user in" do get :index, rss_token: user.rss_token, format: :atom - expect(response).to have_http_status 200 + expect(response).to have_gitlab_http_status 200 expect(response.body).to eq 'authenticated' end end @@ -191,7 +161,7 @@ describe ApplicationController do context 'when the request format is not atom' do it "doesn't log the user in" do get :index, rss_token: user.rss_token - expect(response.status).not_to have_http_status 200 + expect(response.status).not_to have_gitlab_http_status 200 expect(response.body).not_to eq 'authenticated' end end @@ -221,6 +191,20 @@ describe ApplicationController do end end + describe '#set_page_title_header' do + let(:controller) { described_class.new } + + it 'URI encodes UTF-8 characters in the title' do + response = double(headers: {}) + allow_any_instance_of(PageLayoutHelper).to receive(:page_title).and_return('€100 · GitLab') + allow(controller).to receive(:response).and_return(response) + + controller.send(:set_page_title_header) + + expect(response.headers['Page-Title']).to eq('%E2%82%AC100%20%C2%B7%20GitLab') + end + end + context 'two-factor authentication' do let(:controller) { described_class.new } diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index be27bbb4283..73fff6eb5ca 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -30,7 +30,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end end @@ -59,7 +59,7 @@ describe AutocompleteController do get(:users, group_id: 'unknown') end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end end @@ -138,7 +138,7 @@ describe AutocompleteController do get(:users, project_id: project.id) end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end describe 'GET #users with unknown project' do @@ -146,7 +146,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end describe 'GET #users with inaccessible group' do @@ -155,7 +155,7 @@ describe AutocompleteController do get(:users, group_id: user.namespace.id) end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end describe 'GET #users with no project' do diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 5163099cd98..44d504d5852 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -24,7 +24,7 @@ describe Boards::IssuesController do it 'returns a not found 404 response' do list_issues user: user, board: 999, list: list2 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -62,7 +62,7 @@ describe Boards::IssuesController do it 'returns a not found 404 response' do list_issues user: user, board: board, list: 999 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -93,7 +93,7 @@ describe Boards::IssuesController do it 'returns a forbidden 403 response' do list_issues user: user, board: board, list: list2 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -116,7 +116,7 @@ describe Boards::IssuesController do it 'returns a successful 200 response' do create_issue user: user, board: board, list: list1, title: 'New issue' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns the created issue' do @@ -131,7 +131,7 @@ describe Boards::IssuesController do it 'returns an unprocessable entity 422 response' do create_issue user: user, board: board, list: list1, title: nil - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -141,7 +141,7 @@ describe Boards::IssuesController do create_issue user: user, board: board, list: list, title: 'New issue' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -149,7 +149,7 @@ describe Boards::IssuesController do it 'returns a not found 404 response' do create_issue user: user, board: 999, list: list1, title: 'New issue' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -157,7 +157,7 @@ describe Boards::IssuesController do it 'returns a not found 404 response' do create_issue user: user, board: board, list: 999, title: 'New issue' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -166,7 +166,7 @@ describe Boards::IssuesController do it 'returns a forbidden 403 response' do create_issue user: guest, board: board, list: list1, title: 'New issue' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -187,7 +187,7 @@ describe Boards::IssuesController do it 'returns a successful 200 response' do move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'moves issue to the desired list' do @@ -201,19 +201,19 @@ describe Boards::IssuesController do it 'returns a unprocessable entity 422 response for invalid lists' do move user: user, board: board, issue: issue, from_list_id: nil, to_list_id: nil - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end it 'returns a not found 404 response for invalid board id' do move user: user, board: 999, issue: issue, from_list_id: list1.id, to_list_id: list2.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a not found 404 response for invalid issue id' do move user: user, board: board, issue: double(id: 999), from_list_id: list1.id, to_list_id: list2.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -227,7 +227,7 @@ describe Boards::IssuesController do it 'returns a forbidden 403 response' do move user: guest, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index b11fce0fa58..a2b432af23a 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -15,7 +15,7 @@ describe Boards::ListsController do it 'returns a successful 200 response' do read_board_list user: user, board: board - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'application/json' end @@ -39,7 +39,7 @@ describe Boards::ListsController do it 'returns a forbidden 403 response' do read_board_list user: user, board: board - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -60,7 +60,7 @@ describe Boards::ListsController do it 'returns a successful 200 response' do create_board_list user: user, board: board, label_id: label.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns the created list' do @@ -75,7 +75,7 @@ describe Boards::ListsController do it 'returns a not found 404 response' do create_board_list user: user, board: board, label_id: nil - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -85,7 +85,7 @@ describe Boards::ListsController do create_board_list user: user, board: board, label_id: label.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -96,7 +96,7 @@ describe Boards::ListsController do create_board_list user: guest, board: board, label_id: label.id - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -119,7 +119,7 @@ describe Boards::ListsController do it 'returns a successful 200 response' do move user: user, board: board, list: planning, position: 1 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'moves the list to the desired position' do @@ -133,7 +133,7 @@ describe Boards::ListsController do it 'returns an unprocessable entity 422 response' do move user: user, board: board, list: planning, position: 6 - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -141,7 +141,7 @@ describe Boards::ListsController do it 'returns a not found 404 response' do move user: user, board: board, list: 999, position: 1 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -149,7 +149,7 @@ describe Boards::ListsController do it 'returns a forbidden 403 response' do move user: guest, board: board, list: planning, position: 6 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -172,7 +172,7 @@ describe Boards::ListsController do it 'returns a successful 200 response' do remove_board_list user: user, board: board, list: planning - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'removes list from board' do @@ -184,7 +184,7 @@ describe Boards::ListsController do it 'returns a not found 404 response' do remove_board_list user: user, board: board, list: 999 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -192,7 +192,7 @@ describe Boards::ListsController do it 'returns a forbidden 403 response' do remove_board_list user: guest, board: board, list: planning - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -212,7 +212,7 @@ describe Boards::ListsController do it 'returns a successful 200 response' do generate_default_lists user: user, board: board - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns the defaults lists' do @@ -228,7 +228,7 @@ describe Boards::ListsController do generate_default_lists user: user, board: board - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -236,7 +236,7 @@ describe Boards::ListsController do it 'returns a forbidden 403 response' do generate_default_lists user: guest, board: board - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb new file mode 100644 index 00000000000..ba84fbf8564 --- /dev/null +++ b/spec/controllers/concerns/group_tree_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe GroupTree do + let(:group) { create(:group, :public) } + let(:user) { create(:user) } + + controller(ApplicationController) do + # `described_class` is not available in this context + include GroupTree # rubocop:disable RSpec/DescribedClass + + def index + render_group_tree GroupsFinder.new(current_user).execute + end + end + + before do + group.add_owner(user) + sign_in(user) + end + + describe 'GET #index' do + it 'filters groups' do + other_group = create(:group, name: 'filter') + other_group.add_owner(user) + + get :index, filter: 'filt', format: :json + + expect(assigns(:groups)).to contain_exactly(other_group) + end + + context 'for subgroups', :nested_groups do + it 'only renders root groups when no parent was given' do + create(:group, :public, parent: group) + + get :index, format: :json + + expect(assigns(:groups)).to contain_exactly(group) + end + + it 'contains only the subgroup when a parent was given' do + subgroup = create(:group, :public, parent: group) + + get :index, parent_id: group.id, format: :json + + expect(assigns(:groups)).to contain_exactly(subgroup) + end + + it 'allows filtering for subgroups and includes the parents for rendering' do + subgroup = create(:group, :public, parent: group, name: 'filter') + + get :index, filter: 'filt', format: :json + + expect(assigns(:groups)).to contain_exactly(group, subgroup) + end + + it 'does not include groups the user does not have access to' do + parent = create(:group, :private) + subgroup = create(:group, :private, parent: parent, name: 'filter') + subgroup.add_developer(user) + _other_subgroup = create(:group, :private, parent: parent, name: 'filte') + + get :index, filter: 'filt', format: :json + + expect(assigns(:groups)).to contain_exactly(parent, subgroup) + end + end + + context 'json content' do + it 'shows groups as json' do + get :index, format: :json + + expect(json_response.first['id']).to eq(group.id) + end + + context 'nested groups', :nested_groups do + it 'expands the tree when filtering' do + subgroup = create(:group, :public, parent: group, name: 'filter') + + get :index, filter: 'filt', format: :json + + children_response = json_response.first['children'] + + expect(json_response.first['id']).to eq(group.id) + expect(children_response.first['id']).to eq(subgroup.id) + end + end + end + end +end diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb new file mode 100644 index 00000000000..33b23db302a --- /dev/null +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe LfsRequest do + include ProjectForksHelper + + controller(Projects::GitHttpClientController) do + # `described_class` is not available in this context + include LfsRequest # rubocop:disable RSpec/DescribedClass + + def show + storage_project + + render nothing: true + end + + def project + @project ||= Project.find(params[:id]) + end + + def download_request? + true + end + + def ci? + false + end + end + + let(:project) { create(:project, :public) } + + before do + stub_lfs_setting(enabled: true) + end + + describe '#storage_project' do + it 'assigns the project as storage project' do + get :show, id: project.id + + expect(assigns(:storage_project)).to eq(project) + end + + it 'assigns the source of a forked project' do + forked_project = fork_project(project) + + get :show, id: forked_project.id + + expect(assigns(:storage_project)).to eq(project) + end + end +end diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb new file mode 100644 index 00000000000..fb9d3efbac0 --- /dev/null +++ b/spec/controllers/dashboard/groups_controller_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Dashboard::GroupsController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'renders group trees' do + expect(described_class).to include(GroupTree) + end + + it 'only includes projects the user is a member of' do + member_of_group = create(:group) + member_of_group.add_developer(user) + create(:group, :public) + + get :index + + expect(assigns(:groups)).to contain_exactly(member_of_group) + end +end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 2dcb67d50f4..2f3d7be9abe 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -32,7 +32,7 @@ describe Dashboard::MilestonesController do it 'shows milestone page' do view_milestone - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index c8c6b9f41bf..d862e1447e3 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -18,19 +18,19 @@ describe Dashboard::TodosController do get :index, project_id: unauthorized_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_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) + expect(response).to have_gitlab_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) + expect(response).to have_gitlab_http_status(200) end it 'renders 200 when user has access on given project' do @@ -38,7 +38,7 @@ describe Dashboard::TodosController do get :index, project_id: authorized_project.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -57,11 +57,11 @@ describe Dashboard::TodosController do expect(response).to redirect_to(dashboard_todos_path(page: last_page)) end - it 'redirects to correspondent page' do + it 'goes to the correct page' do get :index, page: last_page expect(assigns(:todos).current_page).to eq(last_page) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not redirect to external sites when provided a host field' do @@ -70,6 +70,30 @@ describe Dashboard::TodosController do expect(response).to redirect_to(dashboard_todos_path(page: last_page)) end + + context 'when providing no filters' do + it 'does not perform a query to get the page count, but gets that from the user' do + allow(controller).to receive(:current_user).and_return(user) + + expect(user).to receive(:todos_pending_count).and_call_original + + get :index, page: (last_page + 1).to_param, sort: :created_asc + + expect(response).to redirect_to(dashboard_todos_path(page: last_page, sort: :created_asc)) + end + end + + context 'when providing filters' do + it 'performs a query to get the correct page count' do + allow(controller).to receive(:current_user).and_return(user) + + expect(user).not_to receive(:todos_pending_count) + + get :index, page: (last_page + 1).to_param, project_id: project.id + + expect(response).to redirect_to(dashboard_todos_path(page: last_page, project_id: project.id)) + end + end end end @@ -80,7 +104,7 @@ describe Dashboard::TodosController do patch :restore, id: todo.id expect(todo.reload).to be_pending - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq({ "count" => "1", "done_count" => "0" }) end end @@ -94,7 +118,7 @@ describe Dashboard::TodosController do todos.each do |todo| expect(todo.reload).to be_pending end - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq({ 'count' => '2', 'done_count' => '0' }) end end diff --git a/spec/controllers/explore/groups_controller_spec.rb b/spec/controllers/explore/groups_controller_spec.rb new file mode 100644 index 00000000000..9e0ad9ea86f --- /dev/null +++ b/spec/controllers/explore/groups_controller_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Explore::GroupsController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'renders group trees' do + expect(described_class).to include(GroupTree) + end + + it 'includes public projects' do + member_of_group = create(:group) + member_of_group.add_developer(user) + public_group = create(:group, :public) + + get :index + + expect(assigns(:groups)).to contain_exactly(member_of_group, public_group) + end +end diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb new file mode 100644 index 00000000000..80d553f0f34 --- /dev/null +++ b/spec/controllers/google_api/authorizations_controller_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe GoogleApi::AuthorizationsController do + describe 'GET|POST #callback' do + let(:user) { create(:user) } + let(:token) { 'token' } + let(:expires_at) { 1.hour.since.strftime('%s') } + + subject { get :callback, code: 'xxx', state: @state } + + before do + sign_in(user) + + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:get_token).and_return([token, expires_at]) + end + + it 'sets token and expires_at in session' do + subject + + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) + .to eq(token) + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) + .to eq(expires_at) + end + + context 'when redirect uri key is stored in state' do + set(:project) { create(:project) } + let(:redirect_uri) { project_clusters_url(project).to_s } + + before do + @state = GoogleApi::CloudPlatform::Client + .new_session_key_for_redirect_uri do |key| + session[key] = redirect_uri + end + end + + it 'redirects to the URL stored in state param' do + expect(subject).to redirect_to(redirect_uri) + end + end + + context 'when redirection url is not stored in state' do + it 'redirects to root_path' do + expect(subject).to redirect_to(root_path) + end + end + end +end diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb new file mode 100644 index 00000000000..4262d474e59 --- /dev/null +++ b/spec/controllers/groups/children_controller_spec.rb @@ -0,0 +1,286 @@ +require 'spec_helper' + +describe Groups::ChildrenController do + let(:group) { create(:group, :public) } + let(:user) { create(:user) } + let!(:group_member) { create(:group_member, group: group, user: user) } + + describe 'GET #index' do + context 'for projects' do + let!(:public_project) { create(:project, :public, namespace: group) } + let!(:private_project) { create(:project, :private, namespace: group) } + + context 'as a user' do + before do + sign_in(user) + end + + it 'shows all children' do + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_project, private_project) + end + + context 'being member of private subgroup' do + it 'shows public and private children the user is member of' do + group_member.destroy! + private_project.add_guest(user) + + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_project, private_project) + end + end + end + + context 'as a guest' do + it 'shows the public children' do + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_project) + end + end + end + + context 'for subgroups', :nested_groups do + let!(:public_subgroup) { create(:group, :public, parent: group) } + let!(:private_subgroup) { create(:group, :private, parent: group) } + let!(:public_project) { create(:project, :public, namespace: group) } + let!(:private_project) { create(:project, :private, namespace: group) } + + context 'as a user' do + before do + sign_in(user) + end + + it 'shows all children' do + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project) + end + + context 'being member of private subgroup' do + it 'shows public and private children the user is member of' do + group_member.destroy! + private_subgroup.add_guest(user) + private_project.add_guest(user) + + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project) + end + end + end + + context 'as a guest' do + it 'shows the public children' do + get :index, group_id: group.to_param, format: :json + + expect(assigns(:children)).to contain_exactly(public_subgroup, public_project) + end + end + + context 'filtering children' do + it 'expands the tree for matching projects' do + project = create(:project, :public, namespace: public_subgroup, name: 'filterme') + + get :index, group_id: group.to_param, filter: 'filter', format: :json + + group_json = json_response.first + project_json = group_json['children'].first + + expect(group_json['id']).to eq(public_subgroup.id) + expect(project_json['id']).to eq(project.id) + end + + it 'expands the tree for matching subgroups' do + matched_group = create(:group, :public, parent: public_subgroup, name: 'filterme') + + get :index, group_id: group.to_param, filter: 'filter', format: :json + + group_json = json_response.first + matched_group_json = group_json['children'].first + + expect(group_json['id']).to eq(public_subgroup.id) + expect(matched_group_json['id']).to eq(matched_group.id) + end + + it 'merges the trees correctly' do + shared_subgroup = create(:group, :public, parent: group, path: 'hardware') + matched_project_1 = create(:project, :public, namespace: shared_subgroup, name: 'mobile-soc') + + l2_subgroup = create(:group, :public, parent: shared_subgroup, path: 'broadcom') + l3_subgroup = create(:group, :public, parent: l2_subgroup, path: 'wifi-group') + matched_project_2 = create(:project, :public, namespace: l3_subgroup, name: 'mobile') + + get :index, group_id: group.to_param, filter: 'mobile', format: :json + + shared_group_json = json_response.first + expect(shared_group_json['id']).to eq(shared_subgroup.id) + + matched_project_1_json = shared_group_json['children'].detect { |child| child['type'] == 'project' } + expect(matched_project_1_json['id']).to eq(matched_project_1.id) + + l2_subgroup_json = shared_group_json['children'].detect { |child| child['type'] == 'group' } + expect(l2_subgroup_json['id']).to eq(l2_subgroup.id) + + l3_subgroup_json = l2_subgroup_json['children'].first + expect(l3_subgroup_json['id']).to eq(l3_subgroup.id) + + matched_project_2_json = l3_subgroup_json['children'].first + expect(matched_project_2_json['id']).to eq(matched_project_2.id) + end + + it 'expands the tree upto a specified parent' do + subgroup = create(:group, :public, parent: group) + l2_subgroup = create(:group, :public, parent: subgroup) + create(:project, :public, namespace: l2_subgroup, name: 'test') + + get :index, group_id: subgroup.to_param, filter: 'test', format: :json + + expect(response).to have_http_status(200) + end + + it 'returns an array with one element when only one result is matched' do + create(:project, :public, namespace: group, name: 'match') + + get :index, group_id: group.to_param, filter: 'match', format: :json + + expect(json_response).to be_kind_of(Array) + expect(json_response.size).to eq(1) + end + + it 'returns an empty array when there are no search results' do + subgroup = create(:group, :public, parent: group) + l2_subgroup = create(:group, :public, parent: subgroup) + create(:project, :public, namespace: l2_subgroup, name: 'no-match') + + get :index, group_id: subgroup.to_param, filter: 'test', format: :json + + expect(json_response).to eq([]) + end + + it 'includes pagination headers' do + 2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") } + + get :index, group_id: group.to_param, filter: 'filter', per_page: 1, format: :json + + expect(response).to include_pagination_headers + end + end + + context 'queries per rendered element', :request_store do + # We need to make sure the following counts are preloaded + # otherwise they will cause an extra query + # 1. Count of visible projects in the element + # 2. Count of visible subgroups in the element + # 3. Count of members of a group + let(:expected_queries_per_group) { 0 } + let(:expected_queries_per_project) { 0 } + + def get_list + get :index, group_id: group.to_param, format: :json + end + + it 'queries the expected amount for a group row' do + control = ActiveRecord::QueryRecorder.new { get_list } + + _new_group = create(:group, :public, parent: group) + + expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_group) + end + + it 'queries the expected amount for a project row' do + control = ActiveRecord::QueryRecorder.new { get_list } + _new_project = create(:project, :public, namespace: group) + + expect { get_list }.not_to exceed_query_limit(control).with_threshold(expected_queries_per_project) + end + + context 'when rendering hierarchies' do + # When loading hierarchies we load the all the ancestors for matched projects + # in 1 separate query + let(:extra_queries_for_hierarchies) { 1 } + + def get_filtered_list + get :index, group_id: group.to_param, filter: 'filter', format: :json + end + + it 'queries the expected amount when nested rows are increased for a group' do + matched_group = create(:group, :public, parent: group, name: 'filterme') + + control = ActiveRecord::QueryRecorder.new { get_filtered_list } + + matched_group.update!(parent: public_subgroup) + + expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies) + end + + it 'queries the expected amount when a new group match is added' do + create(:group, :public, parent: public_subgroup, name: 'filterme') + + control = ActiveRecord::QueryRecorder.new { get_filtered_list } + + create(:group, :public, parent: public_subgroup, name: 'filterme2') + create(:group, :public, parent: public_subgroup, name: 'filterme3') + + expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies) + end + + it 'queries the expected amount when nested rows are increased for a project' do + matched_project = create(:project, :public, namespace: group, name: 'filterme') + + control = ActiveRecord::QueryRecorder.new { get_filtered_list } + + matched_project.update!(namespace: public_subgroup) + + expect { get_filtered_list }.not_to exceed_query_limit(control).with_threshold(extra_queries_for_hierarchies) + end + end + end + end + + context 'pagination' do + let(:per_page) { 3 } + + before do + allow(Kaminari.config).to receive(:default_per_page).and_return(per_page) + end + + context 'with only projects' do + let!(:other_project) { create(:project, :public, namespace: group) } + let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group ) } + + it 'has projects on the first page' do + get :index, group_id: group.to_param, sort: 'id_desc', format: :json + + expect(assigns(:children)).to contain_exactly(*first_page_projects) + end + + it 'has projects on the second page' do + get :index, group_id: group.to_param, sort: 'id_desc', page: 2, format: :json + + expect(assigns(:children)).to contain_exactly(other_project) + end + end + + context 'with subgroups and projects', :nested_groups do + let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) } + let!(:other_subgroup) { create(:group, :public, parent: group) } + let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) } + + it 'contains all subgroups' do + get :index, group_id: group.to_param, sort: 'id_asc', format: :json + + expect(assigns(:children)).to contain_exactly(*first_page_subgroups) + end + + it 'contains the project and group on the second page' do + get :index, group_id: group.to_param, sort: 'id_asc', page: 2, format: :json + + expect(assigns(:children)).to contain_exactly(other_subgroup, *next_page_projects.take(per_page - 1)) + end + end + end + end +end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index cce53f6697c..9c6d584f59b 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -8,7 +8,7 @@ describe Groups::GroupMembersController do it 'renders index with 200 status code' do get :index, group_id: group - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:index) end end @@ -30,7 +30,7 @@ describe Groups::GroupMembersController do user_ids: group_user.id, access_level: Gitlab::Access::GUEST - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(group.users).not_to include group_user end end @@ -73,7 +73,7 @@ describe Groups::GroupMembersController do it 'returns 403' do delete :destroy, group_id: group, id: 42 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -86,7 +86,7 @@ describe Groups::GroupMembersController do it 'returns 403' do delete :destroy, group_id: group, id: member - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(group.members).to include member end end @@ -123,7 +123,7 @@ describe Groups::GroupMembersController do it 'returns 404' do delete :leave, group_id: group - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -144,7 +144,7 @@ describe Groups::GroupMembersController do it 'supports json request' do delete :leave, group_id: group, format: :json - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['notice']).to eq "You left the \"#{group.name}\" group." end end @@ -157,7 +157,7 @@ describe Groups::GroupMembersController do it 'cannot removes himself from the group' do delete :leave, group_id: group - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -204,7 +204,7 @@ describe Groups::GroupMembersController do it 'returns 403' do post :approve_access_request, group_id: group, id: 42 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -217,7 +217,7 @@ describe Groups::GroupMembersController do it 'returns 403' do post :approve_access_request, group_id: group, id: member - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(group.members).not_to include member end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 899d8ebd12b..da54aa9054c 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -16,7 +16,7 @@ describe Groups::LabelsController do post :toggle_subscription, group_id: group.to_param, id: label.to_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index fbbc67f3ae0..c1aba46be04 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -35,7 +35,7 @@ describe Groups::MilestonesController do it 'shows group milestones page' do get :index, group_id: group.to_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'as JSON' do @@ -51,7 +51,7 @@ describe Groups::MilestonesController do 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).to have_gitlab_http_status(200) expect(response.content_type).to eq 'application/json' end end @@ -153,7 +153,7 @@ describe Groups::MilestonesController do it 'does not redirect' do get :index, group_id: group.to_param - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -172,7 +172,7 @@ describe Groups::MilestonesController do it 'does not redirect' do get :show, group_id: group.to_param, id: title - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -242,7 +242,7 @@ describe Groups::MilestonesController do group_id: group.to_param, milestone: { title: title } - expect(response).not_to have_http_status(404) + expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do @@ -250,7 +250,7 @@ describe Groups::MilestonesController do group_id: group.to_param, milestone: { title: title } - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -262,7 +262,7 @@ describe Groups::MilestonesController do group_id: redirect_route.path, milestone: { title: title } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 2e0efb57c74..e9f0924caba 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -13,7 +13,7 @@ describe Groups::Settings::CiCdController do it 'renders show with 200 status code' do get :show, group_id: group - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:show) end end diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb index 02f2fa46047..8ea98cd9e8f 100644 --- a/spec/controllers/groups/variables_controller_spec.rb +++ b/spec/controllers/groups/variables_controller_spec.rb @@ -48,7 +48,7 @@ describe Groups::VariablesController do post :update, group_id: group, id: variable.id, variable: { key: '?', value: variable.value } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template :show end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index b0564e27a68..a9cfd964dd5 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' describe GroupsController do let(:user) { create(:user) } @@ -32,6 +32,31 @@ describe GroupsController do end end + describe 'GET #show' do + before do + sign_in(user) + project + end + + context 'as html' do + it 'assigns whether or not a group has children' do + get :show, id: group.to_param + + expect(assigns(:has_children)).to be_truthy + end + end + + context 'as atom' do + it 'assigns events for all the projects in the group' do + create(:event, project: project) + + get :show, id: group.to_param, format: :atom + + expect(assigns(:events)).not_to be_empty + end + end + end + describe 'GET #new' do context 'when creating subgroups', :nested_groups do [true, false].each do |can_create_group_status| @@ -150,42 +175,6 @@ describe GroupsController do end end - describe 'GET #subgroups', :nested_groups do - let!(:public_subgroup) { create(:group, :public, parent: group) } - let!(:private_subgroup) { create(:group, :private, parent: group) } - - context 'as a user' do - before do - sign_in(user) - end - - it 'shows all subgroups' do - get :subgroups, id: group.to_param - - expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup) - end - - context 'being member of private subgroup' do - it 'shows public and private subgroups the user is member of' do - group_member.destroy! - private_subgroup.add_guest(user) - - get :subgroups, id: group.to_param - - expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup) - end - end - end - - context 'as a guest' do - it 'shows the public subgroups' do - get :subgroups, id: group.to_param - - expect(assigns(:nested_groups)).to contain_exactly(public_subgroup) - end - end - end - describe 'GET #issues' do let(:issue_1) { create(:issue, project: project) } let(:issue_2) { create(:issue, project: project) } @@ -274,7 +263,7 @@ describe GroupsController do it 'updates the path successfully' do post :update, id: group.to_param, group: { path: 'new_path' } - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(controller).to set_flash[:notice] end @@ -345,7 +334,7 @@ describe GroupsController do it 'does not redirect' do get :issues, id: group.to_param - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -364,7 +353,7 @@ describe GroupsController do it 'does not redirect' do get :show, id: group.to_param - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -425,62 +414,62 @@ describe GroupsController do end end end - end - context 'for a POST request' do - context 'when requesting the canonical path with different casing' do - it 'does not 404' do - post :update, id: group.to_param.upcase, group: { path: 'new_path' } + context 'for a POST request' do + context 'when requesting the canonical path with different casing' do + it 'does not 404' do + post :update, id: group.to_param.upcase, group: { path: 'new_path' } - expect(response).not_to have_http_status(404) - end + expect(response).not_to have_gitlab_http_status(404) + end - it 'does not redirect to the correct casing' do - post :update, id: group.to_param.upcase, group: { path: 'new_path' } + it 'does not redirect to the correct casing' do + post :update, id: group.to_param.upcase, group: { path: 'new_path' } - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) + end end - end - context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + context 'when requesting a redirected path' do + let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } - it 'returns not found' do - post :update, id: redirect_route.path, group: { path: 'new_path' } + it 'returns not found' do + post :update, id: redirect_route.path, group: { path: 'new_path' } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) + end end end - end - context 'for a DELETE request' do - context 'when requesting the canonical path with different casing' do - it 'does not 404' do - delete :destroy, id: group.to_param.upcase + context 'for a DELETE request' do + context 'when requesting the canonical path with different casing' do + it 'does not 404' do + delete :destroy, id: group.to_param.upcase - expect(response).not_to have_http_status(404) - end + expect(response).not_to have_gitlab_http_status(404) + end - it 'does not redirect to the correct casing' do - delete :destroy, id: group.to_param.upcase + it 'does not redirect to the correct casing' do + delete :destroy, id: group.to_param.upcase - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) + end end - end - context 'when requesting a redirected path' do - let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } + context 'when requesting a redirected path' do + let(:redirect_route) { group.redirect_routes.create(path: 'old-path') } - it 'returns not found' do - delete :destroy, id: redirect_route.path + it 'returns not found' do + delete :destroy, id: redirect_route.path - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) + end end end end - end - def group_moved_message(redirect_route, group) - "Group '#{redirect_route.path}' was moved to '#{group.full_path}'. Please update any links and bookmarks that may still have the old path." + def group_moved_message(redirect_route, group) + "Group '#{redirect_route.path}' was moved to '#{group.full_path}'. Please update any links and bookmarks that may still have the old path." + end end end diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 03da6287774..2cead1770c9 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -100,7 +100,7 @@ describe HealthCheckController do it 'supports failure plaintext response' do get :index - expect(response).to have_http_status(500) + expect(response).to have_gitlab_http_status(500) expect(response.content_type).to eq 'text/plain' expect(response.body).to include('The server is on fire') end @@ -108,7 +108,7 @@ describe HealthCheckController do it 'supports failure json response' do get :index, format: :json - expect(response).to have_http_status(500) + expect(response).to have_gitlab_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('The server is on fire') @@ -117,7 +117,7 @@ describe HealthCheckController do it 'supports failure xml response' do get :index, format: :xml - expect(response).to have_http_status(500) + expect(response).to have_gitlab_http_status(500) expect(response.content_type).to eq 'application/xml' expect(xml_response['healthy']).to be false expect(xml_response['message']).to include('The server is on fire') @@ -126,7 +126,7 @@ describe HealthCheckController do it 'supports failure responses for specific checks' do get :index, checks: 'email', format: :json - expect(response).to have_http_status(500) + expect(response).to have_gitlab_http_status(500) expect(response.content_type).to eq 'application/json' expect(json_response['healthy']).to be false expect(json_response['message']).to include('Email is on fire') diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index d3489324a9c..f75048f422c 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -100,7 +100,7 @@ describe HelpController do context 'for UI Development Kit' do it 'renders found' do get :ui - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index e00403118a0..6c09ca7dc66 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -15,7 +15,7 @@ describe InvitesController do get :accept, id: token member.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(member.user).to eq(user) expect(flash[:notice]).to include 'You have been granted' end @@ -26,7 +26,7 @@ describe InvitesController do get :decline, id: token expect {member.reload}.to raise_error ActiveRecord::RecordNotFound - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(flash[:notice]).to include 'You have declined the invitation to join' end end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index d6f68b73428..9e8a37171ec 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -59,17 +59,6 @@ describe MetricsController do 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_seconds{shard="default"} [0-9\.]+$/) - expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/) - 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_seconds{shard="default"} [0-9\.]+$/) - expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/) - end - context 'prometheus metrics are disabled' do before do allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(false) diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index bef815ee1f7..9014b8b5084 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -110,7 +110,7 @@ describe NotificationSettingsController do project_id: private_project.id, notification_setting: { level: :participating } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -172,7 +172,7 @@ describe NotificationSettingsController do id: notification_setting, notification_setting: { level: :participating } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index 552899eb36c..b38652e7ab9 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -12,7 +12,7 @@ describe Oauth::ApplicationsController do it 'shows list of applications' do get :index - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'redirects back to profile page if OAuth applications are disabled' do @@ -21,7 +21,7 @@ describe Oauth::ApplicationsController do get :index - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(profile_path) end end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index ac7f73c6e81..004b463e745 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -28,7 +28,7 @@ describe Oauth::AuthorizationsController do it 'returns 200 code and renders error view' do get :new - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('doorkeeper/authorizations/error') end end @@ -37,7 +37,7 @@ describe Oauth::AuthorizationsController do it 'returns 200 code and renders view' do get :new, params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('doorkeeper/authorizations/new') end @@ -48,7 +48,7 @@ describe Oauth::AuthorizationsController do get :new, params expect(request.session['user_return_to']).to be_nil - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end end diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb index cdaa88bbf5d..8778bff1190 100644 --- a/spec/controllers/passwords_controller_spec.rb +++ b/spec/controllers/passwords_controller_spec.rb @@ -12,7 +12,7 @@ describe PasswordsController do post :create - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb index d387aba227b..f8d9d7e39ee 100644 --- a/spec/controllers/profiles/accounts_controller_spec.rb +++ b/spec/controllers/profiles/accounts_controller_spec.rb @@ -11,7 +11,7 @@ describe Profiles::AccountsController do it 'renders 404 if someone tries to unlink a non existent provider' do delete :unlink, provider: 'github' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end [:saml, :cas3].each do |provider| @@ -23,7 +23,7 @@ describe Profiles::AccountsController do delete :unlink, provider: provider.to_s - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(user.reload.identities).to include(identity) end end @@ -38,7 +38,7 @@ describe Profiles::AccountsController do delete :unlink, provider: provider.to_s - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(user.reload.identities).not_to include(identity) end end diff --git a/spec/controllers/profiles/emails_controller_spec.rb b/spec/controllers/profiles/emails_controller_spec.rb new file mode 100644 index 00000000000..ecf14aad54f --- /dev/null +++ b/spec/controllers/profiles/emails_controller_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Profiles::EmailsController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + describe '#create' do + let(:email_params) { { email: "add_email@example.com" } } + + it 'sends an email confirmation' do + expect { post(:create, { email: email_params }) }.to change { ActionMailer::Base.deliveries.size } + expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]] + expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions" + end + end + + describe '#resend_confirmation_instructions' do + let(:email_params) { { email: "add_email@example.com" } } + + it 'resends an email confirmation' do + email = user.emails.create(email: 'add_email@example.com') + + expect { put(:resend_confirmation_instructions, { id: email }) }.to change { ActionMailer::Base.deliveries.size } + expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]] + expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions" + end + + it 'unable to resend an email confirmation' do + expect { put(:resend_confirmation_instructions, { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size } + end + end +end diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index b52b63e05a4..d380978b86e 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -1,9 +1,10 @@ require('spec_helper') -describe ProfilesController do - describe "PUT update" do - it "allows an email update from a user without an external email address" do - user = create(:user) +describe ProfilesController, :request_store do + let(:user) { create(:user) } + + describe 'PUT update' do + it 'allows an email update from a user without an external email address' do sign_in(user) put :update, @@ -15,7 +16,21 @@ describe ProfilesController do expect(user.unconfirmed_email).to eq('john@gmail.com') end - it "ignores an email update from a user with an external email address" do + it "allows an email update without confirmation if existing verified email" do + user = create(:user) + create(:email, :confirmed, user: user, email: 'john@gmail.com') + sign_in(user) + + put :update, + user: { email: "john@gmail.com", name: "John" } + + user.reload + + expect(response.status).to eq(302) + expect(user.unconfirmed_email).to eq nil + end + + it 'ignores an email update from a user with an external email address' do stub_omniauth_setting(sync_profile_from_provider: ['ldap']) stub_omniauth_setting(sync_profile_attributes: true) @@ -32,7 +47,7 @@ describe ProfilesController do expect(ldap_user.unconfirmed_email).not_to eq('john@gmail.com') end - it "ignores an email and name update but allows a location update from a user with external email and name, but not external location" do + it 'ignores an email and name update but allows a location update from a user with external email and name, but not external location' do stub_omniauth_setting(sync_profile_from_provider: ['ldap']) stub_omniauth_setting(sync_profile_attributes: true) @@ -51,4 +66,35 @@ describe ProfilesController do expect(ldap_user.location).to eq('City, Country') end end + + describe 'PUT update_username' do + let(:namespace) { user.namespace } + let(:project) { create(:project_empty_repo, namespace: namespace) } + let(:gitlab_shell) { Gitlab::Shell.new } + let(:new_username) { 'renamedtosomethingelse' } + + it 'allows username change' do + sign_in(user) + + put :update_username, + user: { username: new_username } + + user.reload + + expect(response.status).to eq(302) + expect(user.username).to eq(new_username) + end + + it 'moves dependent projects to new namespace' do + sign_in(user) + + put :update_username, + user: { username: new_username } + + user.reload + + expect(response.status).to eq(302) + expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy + end + end end diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index caa63e7bd22..d1051741430 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Projects::ArtifactsController do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } + set(:user) { create(:user) } + set(:project) { create(:project, :repository, :public) } let(:pipeline) do create(:ci_pipeline, @@ -15,7 +15,7 @@ describe Projects::ArtifactsController do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } before do - project.team << [user, :developer] + project.add_developer(user) sign_in(user) end @@ -47,19 +47,67 @@ describe Projects::ArtifactsController do end describe 'GET file' do - context 'when the file exists' do - it 'renders the file view' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + end - expect(response).to render_template('projects/artifacts/file') + context 'when the file is served by GitLab Pages' do + before do + allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) + end + + context 'when the file exists' do + it 'renders the file view' do + get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' + + expect(response).to have_gitlab_http_status(302) + end + end + + context 'when the file does not exist' do + it 'responds Not Found' do + get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + + expect(response).to be_not_found + end end end - context 'when the file does not exist' do - it 'responds Not Found' do - get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + context 'when the file is served through Rails' do + context 'when the file exists' do + it 'renders the file view' do + get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' - expect(response).to be_not_found + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('projects/artifacts/file') + end + end + + context 'when the file does not exist' do + it 'responds Not Found' do + get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' + + expect(response).to be_not_found + end + end + end + + context 'when the project is private' do + let(:private_project) { create(:project, :repository, :private) } + let(:pipeline) { create(:ci_pipeline, project: private_project) } + let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + + before do + private_project.add_developer(user) + + allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) + end + + it 'does not redirect the request' do + get :file, namespace_id: private_project.namespace, project_id: private_project, job_id: job, path: 'ci_artifacts.txt' + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('projects/artifacts/file') end end end @@ -95,7 +143,7 @@ describe Projects::ArtifactsController do context 'cannot find the job' do shared_examples 'not found' do - it { expect(response).to have_http_status(:not_found) } + it { expect(response).to have_gitlab_http_status(:not_found) } end context 'has no such ref' do diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb index d68200164e4..e7cddf8cfbf 100644 --- a/spec/controllers/projects/badges_controller_spec.rb +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -13,13 +13,13 @@ describe Projects::BadgesController do it 'requests the pipeline badge successfully' do get_badge(:pipeline) - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end it 'requests the coverage badge successfully' do get_badge(:coverage) - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end def get_badge(badge) diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index c086b386381..54282aa4001 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -28,7 +28,7 @@ describe Projects::BlameController do context "invalid file" do let(:id) { 'master/files/ruby/missing_file.rb'} - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 64b9af7b845..6a1c07b4a0b 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe Projects::BlobController do + include ProjectForksHelper + let(:project) { create(:project, :public, :repository) } describe "GET show" do @@ -151,7 +153,7 @@ describe Projects::BlobController do end it 'redirects to blob show' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -165,7 +167,7 @@ describe Projects::BlobController do end it 'redirects to blob show' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -226,9 +228,8 @@ describe Projects::BlobController do end context 'when user has forked project' do - let(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } - let!(:forked_project) { forked_project_link.forked_to_project } - let(:guest) { forked_project.owner } + let!(:forked_project) { fork_project(project, guest, namespace: guest.namespace, repository: true) } + let(:guest) { create(:user) } before do sign_in(guest) diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 9e2e9a39481..84cde33d944 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -45,7 +45,7 @@ describe Projects::BoardsController do it 'returns a not found 404 response' do list_boards - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -85,7 +85,7 @@ describe Projects::BoardsController do it 'returns a not found 404 response' do read_board board: board - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -95,7 +95,7 @@ describe Projects::BoardsController do read_board board: another_board - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 5e0b57e9b2e..973d6fed288 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -62,7 +62,7 @@ describe Projects::BranchesController do let(:branch) { "feature%2Ftest" } let(:ref) { "<script>alert('ref');</script>" } it { is_expected.to render_template('new') } - it { project.repository.branch_names.include?('feature/test') } + it { project.repository.branch_exists?('feature/test') } end end @@ -128,7 +128,7 @@ describe Projects::BranchesController do issue_iid: issue.iid expect(response.location).to include(project_new_blob_path(project, branch)) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -161,7 +161,7 @@ describe Projects::BranchesController do it 'returns a successful 200 response' do create_branch name: 'my-branch', ref: 'master' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns the created branch' do @@ -175,7 +175,7 @@ describe Projects::BranchesController do it 'returns an unprocessable entity 422 response' do create_branch name: "<script>alert('merge');</script>", ref: "<script>alert('ref');</script>" - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -202,7 +202,7 @@ describe Projects::BranchesController do namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(303) + expect(response).to have_gitlab_http_status(303) end end @@ -226,28 +226,28 @@ describe Projects::BranchesController do context "valid branch name, valid source" do let(:branch) { "feature" } - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } it { expect(response.body).to be_blank } end context "valid branch name with unencoded slashes" do let(:branch) { "improve/awesome" } - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } it { expect(response.body).to be_blank } end context "valid branch name with encoded slashes" do let(:branch) { "improve%2Fawesome" } - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } it { expect(response.body).to be_blank } end context "invalid branch name, valid ref" do let(:branch) { "no-branch" } - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } it { expect(response.body).to be_blank } end end @@ -263,7 +263,7 @@ describe Projects::BranchesController do expect(json_response).to eql("message" => 'Branch was removed') end - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } end context 'valid branch name with unencoded slashes' do @@ -273,7 +273,7 @@ describe Projects::BranchesController do expect(json_response).to eql('message' => 'Branch was removed') end - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } end context "valid branch name with encoded slashes" do @@ -283,7 +283,7 @@ describe Projects::BranchesController do expect(json_response).to eql('message' => 'Branch was removed') end - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } end context 'invalid branch name, valid ref' do @@ -293,7 +293,7 @@ describe Projects::BranchesController do expect(json_response).to eql('message' => 'No such branch') end - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end end @@ -341,7 +341,7 @@ describe Projects::BranchesController do it 'responds with status 404' do destroy_all_merged - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -379,7 +379,7 @@ describe Projects::BranchesController do project_id: project, format: :html - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb new file mode 100644 index 00000000000..bd924a1c7be --- /dev/null +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -0,0 +1,308 @@ +require 'spec_helper' + +describe Projects::ClustersController do + set(:user) { create(:user) } + set(:project) { create(:project) } + let(:role) { :master } + + before do + project.team << [user, role] + + sign_in(user) + end + + describe 'GET index' do + subject do + get :index, namespace_id: project.namespace, + project_id: project + end + + context 'when cluster is already created' do + let!(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) } + + it 'redirects to show a cluster' do + subject + + expect(response).to redirect_to(project_cluster_path(project, cluster)) + end + end + + context 'when we do not have cluster' do + it 'redirects to create a cluster' do + subject + + expect(response).to redirect_to(new_project_cluster_path(project)) + end + end + end + + describe 'GET login' do + render_views + + subject do + get :login, namespace_id: project.namespace, + project_id: project + end + + context 'when we do have omniauth configured' do + it 'shows login button' do + subject + + expect(response.body).to include('auth_buttons/signin_with_google') + end + end + + context 'when we do not have omniauth configured' do + before do + stub_omniauth_setting(providers: []) + end + + it 'shows notice message' do + subject + + expect(response.body).to include('Ask your GitLab administrator if you want to use this service.') + end + end + end + + shared_examples 'requires to login' do + it 'redirects to create a cluster' do + subject + + expect(response).to redirect_to(login_project_clusters_path(project)) + end + end + + describe 'GET new' do + render_views + + subject do + get :new, namespace_id: project.namespace, + project_id: project + end + + context 'when logged' do + before do + make_logged_in + end + + it 'shows a creation form' do + subject + + expect(response.body).to include('Create cluster') + end + end + + context 'when not logged' do + it_behaves_like 'requires to login' + end + end + + describe 'POST create' do + subject do + post :create, params.merge(namespace_id: project.namespace, + project_id: project) + end + + context 'when not logged' do + let(:params) { {} } + + it_behaves_like 'requires to login' + end + + context 'when logged in' do + before do + make_logged_in + end + + context 'when all required parameters are set' do + let(:params) do + { + cluster: { + gcp_cluster_name: 'new-cluster', + gcp_project_id: '111' + } + } + end + + before do + expect(ClusterProvisionWorker).to receive(:perform_async) { } + end + + it 'creates a new cluster' do + expect { subject }.to change { Gcp::Cluster.count } + + expect(response).to redirect_to(project_cluster_path(project, project.cluster)) + end + end + + context 'when not all required parameters are set' do + render_views + + let(:params) do + { + cluster: { + project_namespace: 'some namespace' + } + } + end + + it 'shows an error message' do + expect { subject }.not_to change { Gcp::Cluster.count } + + expect(response).to render_template(:new) + end + end + end + end + + describe 'GET status' do + let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) } + + subject do + get :status, namespace_id: project.namespace, + project_id: project, + id: cluster, + format: :json + end + + it "responds with matching schema" do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('cluster_status') + end + end + + describe 'GET show' do + render_views + + let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) } + + subject do + get :show, namespace_id: project.namespace, + project_id: project, + id: cluster + end + + context 'when logged as master' do + it "allows to update cluster" do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to include("Save") + end + + it "allows remove integration" do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to include("Remove integration") + end + end + + context 'when logged as developer' do + let(:role) { :developer } + + it "does not allow to access page" do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'PUT update' do + render_views + + let(:service) { project.build_kubernetes_service } + let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project, service: service) } + let(:params) { {} } + + subject do + put :update, params.merge(namespace_id: project.namespace, + project_id: project, + id: cluster) + end + + context 'when logged as master' do + context 'when valid params are used' do + let(:params) do + { + cluster: { enabled: false } + } + end + + it "redirects back to show page" do + subject + + expect(response).to redirect_to(project_cluster_path(project, project.cluster)) + expect(flash[:notice]).to eq('Cluster was successfully updated.') + end + end + + context 'when invalid params are used' do + let(:params) do + { + cluster: { project_namespace: 'my Namespace 321321321 #' } + } + end + + it "rejects changes" do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:show) + end + end + end + + context 'when logged as developer' do + let(:role) { :developer } + + it "does not allow to update cluster" do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'delete update' do + let(:cluster) { create(:gcp_cluster, :created_on_gke, project: project) } + + subject do + delete :destroy, namespace_id: project.namespace, + project_id: project, + id: cluster + end + + context 'when logged as master' do + it "redirects back to clusters list" do + subject + + expect(response).to redirect_to(project_clusters_path(project)) + expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + end + end + + context 'when logged as developer' do + let(:role) { :developer } + + it "does not allow to destroy cluster" do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + def make_logged_in + session[GoogleApi::CloudPlatform::Client.session_key_for_token] = '1234' + session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = in_hour.to_i.to_s + end + + def in_hour + Time.now + 1.hour + end +end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index df53863482d..4612fc6e441 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -157,7 +157,7 @@ describe Projects::CommitController do id: commit.id) expect(response).not_to be_success - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -206,7 +206,7 @@ describe Projects::CommitController do id: master_pickable_commit.id) expect(response).not_to be_success - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -286,7 +286,7 @@ describe Projects::CommitController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -298,7 +298,7 @@ describe Projects::CommitController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -309,7 +309,7 @@ describe Projects::CommitController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -356,7 +356,7 @@ describe Projects::CommitController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index e26731fb691..c459d732507 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -10,9 +10,36 @@ describe Projects::CommitsController do end describe "GET show" do - context "when the ref name ends in .atom" do - render_views + render_views + + context 'with file path' do + before do + get(:show, + namespace_id: project.namespace, + project_id: project, + id: id) + end + + context "valid branch, valid file" do + let(:id) { 'master/README.md' } + + it { is_expected.to respond_with(:success) } + end + + context "valid branch, invalid file" do + let(:id) { 'master/invalid-path.rb' } + it { is_expected.to respond_with(:not_found) } + end + + context "invalid branch, valid file" do + let(:id) { 'invalid-branch/README.md' } + + it { is_expected.to respond_with(:not_found) } + end + end + + context "when the ref name ends in .atom" do context "when the ref does not exist with the suffix" do it "renders as atom" do get(:show, diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index b4f9fd9b7a2..fe5818da0bc 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -133,7 +133,7 @@ describe Projects::CompareController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -145,7 +145,7 @@ describe Projects::CompareController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -156,7 +156,7 @@ describe Projects::CompareController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -166,7 +166,7 @@ describe Projects::CompareController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 3daff1eeea3..3164fd5c143 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -67,7 +67,7 @@ describe Projects::DeploymentsController do it 'returns a empty response 204 resposne' do get :metrics, deployment_params(id: deployment.id) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(response.body).to eq('') end end @@ -142,7 +142,7 @@ describe Projects::DeploymentsController do 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).to have_gitlab_http_status(204) expect(response.body).to eq('') end end diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index fe62898fa9b..3bf676637a2 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -25,7 +25,7 @@ describe Projects::DiscussionsController do it "returns status 404" do post :resolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -42,7 +42,7 @@ describe Projects::DiscussionsController do it "returns status 404" do post :resolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -69,7 +69,7 @@ describe Projects::DiscussionsController do it "returns status 200" do post :resolve, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -86,7 +86,7 @@ describe Projects::DiscussionsController do it "returns status 404" do delete :unresolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -103,7 +103,7 @@ describe Projects::DiscussionsController do it "returns status 404" do delete :unresolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -117,7 +117,7 @@ describe Projects::DiscussionsController do it "returns status 200" do delete :unresolve, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 5a95f4f6199..ff9ab53d8c3 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::EnvironmentsController do it 'responds with status code 200' do get :index, environment_params - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end @@ -59,7 +59,7 @@ describe Projects::EnvironmentsController do end it 'sets the polling interval header' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.headers['Poll-Interval']).to eq("3000") end end @@ -137,7 +137,7 @@ describe Projects::EnvironmentsController do params[:id] = 12345 get :show, params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -155,7 +155,7 @@ describe Projects::EnvironmentsController do patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' }) patch :update, patch_params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -166,7 +166,7 @@ describe Projects::EnvironmentsController do patch :stop, environment_params(format: :json) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -179,7 +179,7 @@ describe Projects::EnvironmentsController do patch :stop, environment_params(format: :json) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq( { 'redirect_url' => project_job_url(project, action) }) @@ -193,7 +193,7 @@ describe Projects::EnvironmentsController do patch :stop, environment_params(format: :json) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq( { 'redirect_url' => project_environment_url(project, environment) }) @@ -206,7 +206,7 @@ describe Projects::EnvironmentsController do it 'responds with a status code 200' do get :terminal, environment_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'loads the terminals for the enviroment' do @@ -220,7 +220,7 @@ describe Projects::EnvironmentsController do it 'responds with a status code 404' do get :terminal, environment_params(id: 666) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -244,7 +244,7 @@ describe Projects::EnvironmentsController do get :terminal_websocket_authorize, environment_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(response.body).to eq('{"workhorse":"response"}') end @@ -254,7 +254,7 @@ describe Projects::EnvironmentsController do it 'returns 404' do get :terminal_websocket_authorize, environment_params(id: 666) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -290,7 +290,7 @@ describe Projects::EnvironmentsController do it 'returns a metrics JSON document' do get :metrics, environment_params(format: :json) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) end end @@ -330,7 +330,7 @@ describe Projects::EnvironmentsController do it 'returns a metrics JSON document' do get :additional_metrics, environment_params(format: :json) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(json_response).to eq({}) end end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index dc8290c438e..1bedb8ebdff 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -89,7 +89,7 @@ describe Projects::ForksController do get_new - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -118,7 +118,7 @@ describe Projects::ForksController do post_create - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(namespace_project_import_path(user.namespace, project)) end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index b4a22a46b51..4dbbaecdd6d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::IssuesController do get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -28,7 +28,7 @@ describe Projects::IssuesController 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 have_gitlab_http_status(200) expect(response).to render_template(:index) end end @@ -45,7 +45,7 @@ describe Projects::IssuesController do it "returns index" do get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns 301 if request path doesn't match project path" do @@ -59,7 +59,7 @@ describe Projects::IssuesController do project.save! get :index, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -89,7 +89,7 @@ describe Projects::IssuesController do page: last_page.to_param expect(assigns(:issues).current_page).to eq(last_page) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not redirect to external sites when provided a host field' do @@ -166,7 +166,7 @@ describe Projects::IssuesController do get :new, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -174,7 +174,7 @@ describe Projects::IssuesController do it 'renders the "new" template' do get :new, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template(:new) end end @@ -207,162 +207,6 @@ describe Projects::IssuesController do end end - describe 'PUT #update' do - before do - sign_in(user) - project.team << [user, :developer] - end - - it_behaves_like 'update invalid issuable', Issue - - context 'changing the assignee' do - it 'limits the attributes exposed on the assignee' do - assignee = create(:user) - project.add_developer(assignee) - - put :update, - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid, - issue: { assignee_ids: [assignee.id] }, - format: :json - body = JSON.parse(response.body) - - expect(body['assignees'].first.keys) - .to match_array(%w(id name username avatar_url state web_url)) - end - end - - context 'Akismet is enabled' do - let(:project) { create(:project_empty_repo, :public) } - - before do - stub_application_setting(recaptcha_enabled: true) - allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true) - end - - context 'when an issue is not identified as spam' do - before do - allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false) - end - - it 'normally updates the issue' do - expect { update_issue(title: 'Foo') }.to change { issue.reload.title }.to('Foo') - end - end - - context 'when an issue is identified as spam' do - before do - allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) - end - - context 'when captcha is not verified' do - def update_spam_issue - update_issue(title: 'Spam Title', description: 'Spam lives here') - end - - before do - allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) - end - - it 'rejects an issue recognized as a spam' do - expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true) - expect { update_spam_issue }.not_to change { issue.reload.title } - end - - it 'rejects an issue recognized as a spam when recaptcha disabled' do - stub_application_setting(recaptcha_enabled: false) - - expect { update_spam_issue }.not_to change { issue.reload.title } - end - - it 'creates a spam log' do - update_spam_issue - - spam_logs = SpamLog.all - - expect(spam_logs.count).to eq(1) - expect(spam_logs.first.title).to eq('Spam Title') - expect(spam_logs.first.recaptcha_verified).to be_falsey - end - - context 'as HTML' do - it 'renders verify template' do - update_spam_issue - - expect(response).to render_template(:verify) - end - end - - context 'as JSON' do - before do - update_issue({ title: 'Spam Title', description: 'Spam lives here' }, format: :json) - end - - it 'renders json errors' do - expect(json_response) - .to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."]) - end - - it 'returns 422 status' do - expect(response).to have_http_status(422) - end - end - end - - context 'when captcha is verified' do - let(:spammy_title) { 'Whatever' } - let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) } - - def update_verified_issue - update_issue({ title: spammy_title }, - { spam_log_id: spam_logs.last.id, - recaptcha_verification: true }) - end - - before do - allow_any_instance_of(described_class).to receive(:verify_recaptcha) - .and_return(true) - end - - it 'redirect to issue page' do - update_verified_issue - - expect(response) - .to redirect_to(project_issue_path(project, issue)) - end - - it 'accepts an issue after recaptcha is verified' do - expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title) - end - - it 'marks spam log as recaptcha_verified' do - expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true) - end - - it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do - spam_log = create(:spam_log) - - expect { update_issue(spam_log_id: spam_log.id, recaptcha_verification: true) } - .not_to change { SpamLog.last.recaptcha_verified } - end - end - end - - def update_issue(issue_params = {}, additional_params = {}) - params = { - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid, - issue: issue_params - }.merge(additional_params) - - put :update, params - end - end - end - describe 'POST #move' do before do sign_in(user) @@ -380,7 +224,7 @@ describe Projects::IssuesController do it 'moves issue to another project' do move_issue - expect(response).to have_http_status :ok + expect(response).to have_gitlab_http_status :ok expect(another_project.issues).not_to be_empty end end @@ -389,7 +233,7 @@ describe Projects::IssuesController do it 'responds with 404' do move_issue - expect(response).to have_http_status :not_found + expect(response).to have_gitlab_http_status :not_found end end @@ -404,6 +248,45 @@ describe Projects::IssuesController do end end + describe 'PUT #update' do + subject do + put :update, + namespace_id: project.namespace, + project_id: project, + id: issue.to_param, + issue: { title: 'New title' }, format: :json + end + + before do + sign_in(user) + end + + context 'when user has access to update issue' do + before do + project.add_developer(user) + end + + it 'updates the issue' do + subject + + expect(response).to have_http_status(:ok) + expect(issue.reload.title).to eq('New title') + end + end + + context 'when user does not have access to update issue' do + before do + project.add_guest(user) + end + + it 'responds with 404' do + subject + + expect(response).to have_http_status(:not_found) + end + end + end + describe 'Confidential Issues' do let(:project) { create(:project_empty_repo, :public) } let(:assignee) { create(:assignee) } @@ -485,14 +368,14 @@ describe Projects::IssuesController do sign_out(:user) go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status :not_found + expect(response).to have_gitlab_http_status :not_found end it 'returns 404 for non project members' do sign_in(non_member) go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status :not_found + expect(response).to have_gitlab_http_status :not_found end it 'returns 404 for project members with guest role' do @@ -500,21 +383,21 @@ describe Projects::IssuesController do project.team << [member, :guest] go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status :not_found + expect(response).to have_gitlab_http_status :not_found end it "returns #{http_status[:success]} for author" do sign_in(author) go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status http_status[:success] + expect(response).to have_gitlab_http_status http_status[:success] end it "returns #{http_status[:success]} for assignee" do sign_in(assignee) go(id: request_forgery_timing_attack.to_param) - expect(response).to have_http_status http_status[:success] + expect(response).to have_gitlab_http_status http_status[:success] end it "returns #{http_status[:success]} for project members" do @@ -522,14 +405,154 @@ describe Projects::IssuesController do project.team << [member, :developer] go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status http_status[:success] + expect(response).to have_gitlab_http_status http_status[:success] end it "returns #{http_status[:success]} for admin" do sign_in(admin) go(id: unescaped_parameter_value.to_param) - expect(response).to have_http_status http_status[:success] + expect(response).to have_gitlab_http_status http_status[:success] + end + end + + describe 'PUT #update' do + def update_issue(issue_params: {}, additional_params: {}, id: nil) + id ||= issue.iid + params = { + namespace_id: project.namespace.to_param, + project_id: project, + id: id, + issue: { title: 'New title' }.merge(issue_params), + format: :json + }.merge(additional_params) + + put :update, params + end + + def go(id:) + update_issue(id: id) + end + + before do + sign_in(user) + project.team << [user, :developer] + end + + it_behaves_like 'restricted action', success: 200 + it_behaves_like 'update invalid issuable', Issue + + context 'changing the assignee' do + it 'limits the attributes exposed on the assignee' do + assignee = create(:user) + project.add_developer(assignee) + + update_issue(issue_params: { assignee_ids: [assignee.id] }) + + body = JSON.parse(response.body) + + expect(body['assignees'].first.keys) + .to match_array(%w(id name username avatar_url state web_url)) + end + end + + context 'Akismet is enabled' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + stub_application_setting(recaptcha_enabled: true) + allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true) + end + + context 'when an issue is not identified as spam' do + before do + allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) + allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false) + end + + it 'normally updates the issue' do + expect { update_issue(issue_params: { title: 'Foo' }) }.to change { issue.reload.title }.to('Foo') + end + end + + context 'when an issue is identified as spam' do + before do + allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true) + end + + context 'when captcha is not verified' do + before do + allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) + end + + it 'rejects an issue recognized as a spam' do + expect { update_issue }.not_to change { issue.reload.title } + end + + it 'rejects an issue recognized as a spam when recaptcha disabled' do + stub_application_setting(recaptcha_enabled: false) + + expect { update_issue }.not_to change { issue.reload.title } + end + + it 'creates a spam log' do + update_issue(issue_params: { title: 'Spam title' }) + + spam_logs = SpamLog.all + + expect(spam_logs.count).to eq(1) + expect(spam_logs.first.title).to eq('Spam title') + expect(spam_logs.first.recaptcha_verified).to be_falsey + end + + it 'renders json errors' do + update_issue + + expect(json_response) + .to eql("errors" => ["Your issue has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."]) + end + + it 'returns 422 status' do + update_issue + + expect(response).to have_gitlab_http_status(422) + end + end + + context 'when captcha is verified' do + let(:spammy_title) { 'Whatever' } + let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) } + + def update_verified_issue + update_issue( + issue_params: { title: spammy_title }, + additional_params: { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) + end + + before do + allow_any_instance_of(described_class).to receive(:verify_recaptcha) + .and_return(true) + end + + it 'returns 200 status' do + expect(response).to have_gitlab_http_status(200) + end + + it 'accepts an issue after recaptcha is verified' do + expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title) + end + + it 'marks spam log as recaptcha_verified' do + expect { update_verified_issue }.to change { SpamLog.last.recaptcha_verified }.from(false).to(true) + end + + it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do + spam_log = create(:spam_log) + + expect { update_issue(issue_params: { spam_log_id: spam_log.id, recaptcha_verification: true }) } + .not_to change { SpamLog.last.recaptcha_verified } + end + end + end end end @@ -569,7 +592,7 @@ describe Projects::IssuesController do it 'returns 200' do go(id: issue.iid) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -817,7 +840,7 @@ describe Projects::IssuesController do it "rejects a developer to destroy an issue" do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -833,12 +856,12 @@ describe Projects::IssuesController do it "deletes the issue" do delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) 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 - expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once + expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(issue, owner).once delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid end @@ -857,7 +880,7 @@ describe Projects::IssuesController do project_id: project, id: issue.iid, name: "thumbsup") end.to change { issue.award_emoji.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -889,47 +912,48 @@ describe Projects::IssuesController do describe 'GET #discussions' do let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } - - before do - project.add_developer(user) - sign_in(user) - end - - it 'returns discussion json' do - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid - - expect(JSON.parse(response.body).first.keys).to match_array(%w[id reply_id expanded notes individual_note]) - end - - context 'with cross-reference system note', :request_store do - let(:new_issue) { create(:issue) } - let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } - + context 'when authenticated' do before do - create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference) + project.add_developer(user) + sign_in(user) end - it 'filters notes that the user should not see' do + it 'returns discussion json' do get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid - expect(JSON.parse(response.body).count).to eq(1) + expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes individual_note]) end - it 'does not result in N+1 queries' do - # Instantiate the controller variables to ensure QueryRecorder has an accurate base count - get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + context 'with cross-reference system note', :request_store do + let(:new_issue) { create(:issue) } + let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } - RequestStore.clear! + before do + create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference) + end - control_count = ActiveRecord::QueryRecorder.new do + it 'filters notes that the user should not see' do get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid - end.count - RequestStore.clear! + expect(JSON.parse(response.body).count).to eq(1) + end + + it 'does not result in N+1 queries' do + # Instantiate the controller variables to ensure QueryRecorder has an accurate base count + get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + + RequestStore.clear! - create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) + control_count = ActiveRecord::QueryRecorder.new do + get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid + end.count - expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count) + RequestStore.clear! + + create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) + + expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count) + end end end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index fdd7e6f173f..f9688949a19 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::JobsController do end it 'has only pending builds' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:builds).first.status).to eq('pending') end end @@ -33,7 +33,7 @@ describe Projects::JobsController do end it 'has only running jobs' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:builds).first.status).to eq('running') end end @@ -46,7 +46,7 @@ describe Projects::JobsController do end it 'has only finished jobs' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:builds).first.status).to eq('success') end end @@ -62,7 +62,7 @@ describe Projects::JobsController do end it 'redirects to the page' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:builds).current_page).to eq(last_page) end end @@ -107,7 +107,7 @@ describe Projects::JobsController do end it 'has a job' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:build).id).to eq(job.id) end end @@ -118,7 +118,7 @@ describe Projects::JobsController do end it 'renders not_found' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -136,7 +136,7 @@ describe Projects::JobsController do end it 'exposes needed information' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['raw_path']).to match(/jobs\/\d+\/raw\z/) expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/) expect(json_response['new_issue_path']) @@ -163,7 +163,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :trace, pipeline: pipeline) } it 'returns a trace' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq job.id expect(json_response['status']).to eq job.status expect(json_response['html']).to eq('BUILD TRACE') @@ -174,7 +174,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, pipeline: pipeline) } it 'returns no traces' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq job.id expect(json_response['status']).to eq job.status expect(json_response['html']).to be_nil @@ -185,7 +185,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :unicode_trace, pipeline: pipeline) } it 'returns a trace with Unicode' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq job.id expect(json_response['status']).to eq job.status expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ") @@ -212,11 +212,11 @@ describe Projects::JobsController do end it 'return a detailed job status in json' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" + expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.ico" end end @@ -232,7 +232,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :retryable, pipeline: pipeline) } it 'redirects to the retried job page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_job_path(id: Ci::Build.last.id)) end end @@ -241,7 +241,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, pipeline: pipeline) } it 'renders unprocessable_entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end @@ -268,7 +268,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :playable, pipeline: pipeline) } it 'redirects to the played job page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end @@ -281,7 +281,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, pipeline: pipeline) } it 'renders unprocessable_entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end @@ -304,7 +304,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } it 'redirects to the canceled job page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end @@ -317,7 +317,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } it 'returns unprocessable_entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end @@ -342,7 +342,7 @@ describe Projects::JobsController do end it 'redirects to a index page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_jobs_path) end @@ -359,7 +359,7 @@ describe Projects::JobsController do end it 'redirects to a index page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_jobs_path) end end @@ -382,7 +382,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline) } it 'redirects to the erased job page' do - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end @@ -400,7 +400,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :erased, pipeline: pipeline) } it 'returns unprocessable_entity' do - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end @@ -420,7 +420,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, :trace, pipeline: pipeline) } it 'send a trace file' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.content_type).to eq 'text/plain; charset=utf-8' expect(response.body).to eq 'BUILD TRACE' end @@ -430,7 +430,7 @@ describe Projects::JobsController do let(:job) { create(:ci_build, pipeline: pipeline) } it 'returns not_found' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index f4e2dca883d..cf83f2f3265 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -78,7 +78,7 @@ describe Projects::LabelsController do it 'creates labels' do post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -86,7 +86,7 @@ describe Projects::LabelsController do it 'creates labels' do post :generate, namespace_id: project.namespace.to_param, project_id: project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end end @@ -97,7 +97,7 @@ describe Projects::LabelsController do toggle_subscription(label) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'allows user to toggle subscription on group labels' do @@ -105,7 +105,7 @@ describe Projects::LabelsController do toggle_subscription(group_label) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end def toggle_subscription(label) @@ -121,7 +121,7 @@ describe Projects::LabelsController do it 'denies access' do post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -170,7 +170,7 @@ describe Projects::LabelsController do it 'does not redirect' do get :index, namespace_id: project.namespace, project_id: project.to_param - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -203,13 +203,13 @@ describe Projects::LabelsController do it 'does not 404' do post :generate, namespace_id: project.namespace, project_id: project - expect(response).not_to have_http_status(404) + expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do post :generate, namespace_id: project.namespace, project_id: project - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -219,7 +219,7 @@ describe Projects::LabelsController do it 'returns not found' do post :generate, namespace_id: project.namespace, project_id: project.to_param + 'old' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 4eea7041d29..33d48ff94d1 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -20,7 +20,7 @@ describe Projects::MattermostsController do namespace_id: project.namespace.to_param, project_id: project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index 393d38c6e6b..2d7647a6e12 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -17,8 +17,8 @@ describe Projects::MergeRequests::ConflictsController do 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) + allow(Gitlab::Git::Conflict::Parser).to receive(:parse) + .and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile) get :show, namespace_id: merge_request_with_conflicts.project.namespace.to_param, @@ -28,7 +28,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end it 'returns JSON with a message' do @@ -109,14 +109,14 @@ describe Projects::MergeRequests::ConflictsController 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) + allow(Gitlab::Git::Conflict::Parser).to receive(:parse) + .and_raise(Gitlab::Git::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) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -126,7 +126,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 404 status code' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -138,7 +138,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 200 status code' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end it 'returns the file in JSON format' do @@ -198,7 +198,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns an OK response' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end @@ -224,7 +224,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end it 'has a message with the name of the first missing section' do @@ -254,7 +254,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end it 'has a message with the name of the missing file' do @@ -292,7 +292,7 @@ describe Projects::MergeRequests::ConflictsController do end it 'returns a 400 error' do - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end it 'has a message with the path of the problem file' do diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index fc4cec53374..7fdddc02fd3 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -112,7 +112,7 @@ describe Projects::MergeRequests::CreationsController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) 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 index fad2c8f3ab7..18a70bec103 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::MergeRequests::DiffsController do + include ProjectForksHelper + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -37,12 +39,12 @@ describe Projects::MergeRequests::DiffsController do render_views let(:project) { create(:project, :repository) } - 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) } + let(:forked_project) { fork_project_with_submodules(project) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_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 + project.add_developer(user) + merge_request.reload go end @@ -117,7 +119,7 @@ describe Projects::MergeRequests::DiffsController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -129,7 +131,7 @@ describe Projects::MergeRequests::DiffsController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -140,7 +142,7 @@ describe Projects::MergeRequests::DiffsController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -153,7 +155,7 @@ describe Projects::MergeRequests::DiffsController do end it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6775012bab5..bfdad85c082 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::MergeRequestsController do + include ProjectForksHelper + let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -81,33 +83,21 @@ describe Projects::MergeRequestsController do end describe 'as json' do - context 'with basic param' do + context 'with basic serializer param' do it 'renders basic MR entity as json' do - go(basic: true, format: :json) + go(serializer: 'basic', format: :json) expect(response).to match_response_schema('entities/merge_request_basic') end end - context 'without basic param' do + context 'without basic serializer param' do it 'renders the merge request in the json format' do go(format: :json) expect(response).to match_response_schema('entities/merge_request') end end - - context 'number of queries', :request_store do - it 'verifies number of queries' do - # pre-create objects - merge_request - - recorded = ActiveRecord::QueryRecorder.new { go(format: :json) } - - expect(recorded.count).to be_within(5).of(30) - expect(recorded.cached_count).to eq(0) - end - end end describe "as diff" do @@ -153,7 +143,7 @@ describe Projects::MergeRequestsController do get_merge_requests(last_page) expect(assigns(:merge_requests).current_page).to eq(last_page) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not redirect to external sites when provided a host field' do @@ -196,17 +186,23 @@ describe Projects::MergeRequestsController do end describe 'PUT update' do + def update_merge_request(mr_params, additional_params = {}) + params = { + namespace_id: project.namespace, + project_id: project, + id: merge_request.iid, + merge_request: mr_params + }.merge(additional_params) + + put :update, params + end + context 'changing the assignee' do it 'limits the attributes exposed on the assignee' do assignee = create(:user) project.add_developer(assignee) - put :update, - namespace_id: project.namespace.to_param, - project_id: project, - id: merge_request.iid, - merge_request: { assignee_id: assignee.id }, - format: :json + update_merge_request({ assignee_id: assignee.id }, format: :json) body = JSON.parse(response.body) expect(body['assignee'].keys) @@ -214,26 +210,31 @@ describe Projects::MergeRequestsController do end end + context 'when user does not have access to update issue' do + before do + reporter = create(:user) + project.add_reporter(reporter) + sign_in(reporter) + end + + it 'responds with 404' do + update_merge_request(title: 'New title') + + expect(response).to have_http_status(:not_found) + end + end + context 'there is no source project' do let(:project) { create(:project, :repository) } - let(:fork_project) { create(:forked_project_with_submodules) } - let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } + let(:forked_project) { fork_project_with_submodules(project) } + let!(:merge_request) { create(:merge_request, source_project: forked_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 - fork_project.destroy + forked_project.destroy end it 'closes MR without errors' do - post :update, - namespace_id: project.namespace, - project_id: project, - id: merge_request.iid, - merge_request: { - state_event: 'close' - } + update_merge_request(state_event: 'close') expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) expect(merge_request.reload.closed?).to be_truthy @@ -242,13 +243,7 @@ describe Projects::MergeRequestsController do it 'allows editing of a closed merge request' do merge_request.close! - put :update, - namespace_id: project.namespace, - project_id: project, - id: merge_request.iid, - merge_request: { - title: 'New title' - } + update_merge_request(title: 'New title') expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) expect(merge_request.reload.title).to eq 'New title' @@ -257,13 +252,7 @@ describe Projects::MergeRequestsController do it 'does not allow to update target branch closed merge request' do merge_request.close! - put :update, - namespace_id: project.namespace, - project_id: project, - id: merge_request.iid, - merge_request: { - target_branch: 'new_branch' - } + update_merge_request(target_branch: 'new_branch') expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch } end @@ -291,7 +280,7 @@ describe Projects::MergeRequestsController do end it 'returns 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -447,7 +436,7 @@ describe Projects::MergeRequestsController do it "denies access to users unless they're admin or project owner" do delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "when the user is owner" do @@ -462,12 +451,12 @@ describe Projects::MergeRequestsController do it "deletes the merge request" do delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) 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 - expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once + expect_any_instance_of(TodoService).to receive(:destroy_issuable).with(merge_request, owner).once delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid end @@ -552,7 +541,7 @@ describe Projects::MergeRequestsController do subject end - it { is_expected.to have_http_status(:success) } + it { is_expected.to have_gitlab_http_status(:success) } it 'renders MergeRequest as JSON' do subject @@ -611,21 +600,16 @@ describe Projects::MergeRequestsController do describe 'GET ci_environments_status' do context 'the environment is from a forked project' do - let!(:forked) { create(:project, :repository) } + let!(:forked) { fork_project(project, user, repository: true) } let!(:environment) { create(:environment, project: forked) } let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } let(:admin) { create(:admin) } let(:merge_request) do - create(:forked_project_link, forked_to_project: forked, - forked_from_project: project) - create(:merge_request, source_project: forked, target_project: project) end before do - forked.team << [user, :master] - get :ci_environments_status, namespace_id: merge_request.project.namespace.to_param, project_id: merge_request.project, @@ -654,11 +638,11 @@ describe Projects::MergeRequestsController do end it 'return a detailed head_pipeline status in json' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" + expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.ico" end end @@ -668,7 +652,7 @@ describe Projects::MergeRequestsController do end it 'return empty' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_empty end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 62f1fb1f697..209979e642d 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -27,7 +27,7 @@ describe Projects::MilestonesController do it 'shows milestone page' do view_milestone - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -86,4 +86,32 @@ describe Projects::MilestonesController do expect(last_note).to eq('removed milestone') end end + + describe '#promote' do + context 'promotion succeeds' do + before do + group = create(:group) + group.add_developer(user) + milestone.project.update(namespace: group) + end + + it 'shows group milestone' do + post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + + group_milestone = assigns(:milestone) + + expect(response).to redirect_to(group_milestone_path(project.group, group_milestone.iid)) + expect(flash[:notice]).to eq('Milestone has been promoted to group milestone.') + end + end + + context 'promotion fails' do + it 'shows project milestone' do + post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid + + expect(response).to redirect_to(project_milestone_path(project, milestone)) + expect(flash[:alert]).to eq('Promotion failed - Project does not belong to a group.') + end + end + end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 6ffe41b8608..5f5a789d5cc 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::NotesController do + include ProjectForksHelper + let(:user) { create(:user) } let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } @@ -57,6 +59,7 @@ describe Projects::NotesController do expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil expect(note_json[:diff_discussion_html]).to be_nil + expect(note_json[:discussion_line_code]).to be_nil end end @@ -72,6 +75,7 @@ describe Projects::NotesController do expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil expect(note_json[:diff_discussion_html]).not_to be_nil + expect(note_json[:discussion_line_code]).not_to be_nil end end @@ -90,6 +94,7 @@ describe Projects::NotesController do expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).not_to be_nil expect(note_json[:diff_discussion_html]).to be_nil + expect(note_json[:discussion_line_code]).to be_nil end end @@ -102,6 +107,20 @@ describe Projects::NotesController do expect(note_json[:id]).to eq(note.id) expect(note_json[:discussion_html]).to be_nil expect(note_json[:diff_discussion_html]).to be_nil + expect(note_json[:discussion_line_code]).to be_nil + end + + context 'when user cannot read commit' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :download_code, project).and_return(false) + end + + it 'renders 404' do + get :index, params + + expect(response).to have_gitlab_http_status(404) + end end end end @@ -118,6 +137,41 @@ describe Projects::NotesController do expect(note_json[:html]).not_to be_nil expect(note_json[:discussion_html]).to be_nil expect(note_json[:diff_discussion_html]).to be_nil + expect(note_json[:discussion_line_code]).to be_nil + end + end + + context 'with cross-reference system note', :request_store do + let(:new_issue) { create(:issue) } + let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" } + + before do + note + create(:discussion_note_on_issue, :system, noteable: issue, project: issue.project, note: cross_reference) + end + + it 'filters notes that the user should not see' do + get :index, request_params + + expect(parsed_response[:notes].count).to eq(1) + expect(note_json[:id]).to eq(note.id) + end + + it 'does not result in N+1 queries' do + # Instantiate the controller variables to ensure QueryRecorder has an accurate base count + get :index, request_params + + RequestStore.clear! + + control_count = ActiveRecord::QueryRecorder.new do + get :index, request_params + end.count + + RequestStore.clear! + + create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference) + + expect { get :index, request_params }.not_to exceed_query_limit(control_count) end end end @@ -144,13 +198,13 @@ describe Projects::NotesController do it "returns status 302 for html" do post :create, request_params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end it "returns status 200 for json" do post :create, request_params.merge(format: :json) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when merge_request_diff_head_sha present' do @@ -169,25 +223,23 @@ describe Projects::NotesController do it "returns status 302 for html" do post :create, request_params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end context 'when creating a commit comment from an MR fork' do let(:project) { create(:project, :repository) } - let(:fork_project) do - create(:project, :repository).tap do |fork| - create(:forked_project_link, forked_to_project: fork, forked_from_project: project) - end + let(:forked_project) do + fork_project(project, nil, repository: true) end let(:merge_request) do - create(:merge_request, source_project: fork_project, target_project: project, source_branch: 'feature', target_branch: 'master') + create(:merge_request, source_project: forked_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) + create(:note_on_commit, note: 'a note', project: forked_project, commit_id: merge_request.commit_shas.first) end def post_create(extra_params = {}) @@ -197,7 +249,7 @@ describe Projects::NotesController do project_id: project, target_type: 'merge_request', target_id: merge_request.id, - note_project_id: fork_project.id, + note_project_id: forked_project.id, in_reply_to_discussion_id: existing_comment.discussion_id }.merge(extra_params) end @@ -206,7 +258,7 @@ describe Projects::NotesController do it 'returns a 404' do post_create(note_project_id: Project.maximum(:id).succ) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -214,21 +266,71 @@ describe Projects::NotesController do it 'returns a 404' do post_create - expect(response).to have_http_status(404) + expect(response).to have_gitlab_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) } + let(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) } before do - fork_project.add_developer(user) + forked_project.add_developer(user) existing_comment end it 'creates the note' do - expect { post_create }.to change { fork_project.notes.count }.by(1) + expect { post_create }.to change { forked_project.notes.count }.by(1) + end + end + end + + context 'when the merge request discussion is locked' do + before do + project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) + merge_request.update_attribute(:discussion_locked, true) + end + + context 'when a noteable is not found' do + it 'returns 404 status' do + request_params[:note][:noteable_id] = 9999 + post :create, request_params.merge(format: :json) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when a user is a team member' do + it 'returns 302 status for html' do + post :create, request_params + + expect(response).to have_gitlab_http_status(302) + end + + it 'returns 200 status for json' do + post :create, request_params.merge(format: :json) + + expect(response).to have_gitlab_http_status(200) + end + + it 'creates a new note' do + expect { post :create, request_params }.to change { Note.count }.by(1) + end + end + + context 'when a user is not a team member' do + before do + project.project_member(user).destroy + end + + it 'returns 404 status' do + post :create, request_params + + expect(response).to have_gitlab_http_status(404) + end + + it 'does not create a new note' do + expect { post :create, request_params }.not_to change { Note.count } end end end @@ -253,7 +355,7 @@ describe Projects::NotesController do it "returns status 200 for html" do delete :destroy, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "deletes the note" do @@ -270,7 +372,7 @@ describe Projects::NotesController do it "returns status 404" do delete :destroy, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -286,7 +388,7 @@ describe Projects::NotesController do post(:toggle_award_emoji, request_params.merge(name: "thumbsup")) end.to change { note.award_emoji.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "removes the already awarded emoji" do @@ -296,7 +398,7 @@ describe Projects::NotesController do post(:toggle_award_emoji, request_params.merge(name: "thumbsup")) end.to change { AwardEmoji.count }.by(-1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -314,7 +416,7 @@ describe Projects::NotesController do it "returns status 404" do post :resolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -331,7 +433,7 @@ describe Projects::NotesController do it "returns status 404" do post :resolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -358,7 +460,7 @@ describe Projects::NotesController do it "returns status 200" do post :resolve, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -375,7 +477,7 @@ describe Projects::NotesController do it "returns status 404" do delete :unresolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -392,7 +494,7 @@ describe Projects::NotesController do it "returns status 404" do delete :unresolve, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -406,7 +508,7 @@ describe Projects::NotesController do it "returns status 200" do delete :unresolve, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index 83c7744a231..4705c50de7e 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -21,7 +21,7 @@ describe Projects::PagesController do it 'returns 200 status' do get :show, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when the project is in a subgroup' do @@ -31,7 +31,7 @@ describe Projects::PagesController do it 'returns a 404 status code' do get :show, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -40,7 +40,7 @@ describe Projects::PagesController do it 'returns 302 status' do delete :destroy, request_params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -53,7 +53,7 @@ describe Projects::PagesController do it 'returns 404 status' do get :show, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -61,7 +61,7 @@ describe Projects::PagesController do it 'returns 404 status' do delete :destroy, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index ad4d7da3bdd..e9e7d357d9c 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -26,7 +26,7 @@ describe Projects::PagesDomainsController do it "displays the 'show' page" do get(:show, request_params.merge(id: pages_domain.domain)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end end @@ -35,7 +35,7 @@ describe Projects::PagesDomainsController do it "displays the 'new' page" do get(:new, request_params) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('new') end end @@ -69,7 +69,7 @@ describe Projects::PagesDomainsController do it 'returns 404 status' do get(:show, request_params.merge(id: pages_domain.domain)) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -77,7 +77,7 @@ describe Projects::PagesDomainsController do it 'returns 404 status' do get :new, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -85,7 +85,7 @@ describe Projects::PagesDomainsController do it "returns 404 status" do post(:create, request_params.merge(pages_domain: pages_domain_params)) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -93,7 +93,7 @@ describe Projects::PagesDomainsController do it "deletes the pages domain" do delete(:destroy, request_params.merge(id: pages_domain.domain)) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 4ac0559c679..4e52e261920 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -15,7 +15,7 @@ describe Projects::PipelineSchedulesController do it 'renders the index view' do visit_pipelines_schedules - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template(:index) end @@ -35,7 +35,7 @@ describe Projects::PipelineSchedulesController do end it 'only shows active pipeline schedules' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(assigns(:schedules)).to include(pipeline_schedule) expect(assigns(:schedules)).not_to include(inactive_pipeline_schedule) end @@ -57,7 +57,7 @@ describe Projects::PipelineSchedulesController do 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(response).to have_gitlab_http_status(:ok) expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule) end end @@ -87,7 +87,7 @@ describe Projects::PipelineSchedulesController do .to change { Ci::PipelineSchedule.count }.by(1) .and change { Ci::PipelineScheduleVariable.count }.by(1) - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) Ci::PipelineScheduleVariable.last.tap do |v| expect(v.key).to eq("AAA") @@ -158,7 +158,7 @@ describe Projects::PipelineSchedulesController do expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1) pipeline_schedule.reload - expect(response).to have_http_status(:found) + expect(response).to have_gitlab_http_status(:found) expect(pipeline_schedule.variables.last.key).to eq('AAA') expect(pipeline_schedule.variables.last.value).to eq('AAA123') end @@ -324,7 +324,7 @@ describe Projects::PipelineSchedulesController do 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(response).to have_gitlab_http_status(:ok) expect(assigns(:schedule)).to eq(pipeline_schedule) end end @@ -376,7 +376,7 @@ describe Projects::PipelineSchedulesController do end it 'does not delete the pipeline schedule' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -391,7 +391,7 @@ describe Projects::PipelineSchedulesController do delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id end.to change { project.pipeline_schedules.count }.by(-1) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index f9d77c7ad03..1604a2da485 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -3,33 +3,37 @@ require 'spec_helper' describe Projects::PipelinesController do include ApiHelpers - let(:user) { create(:user) } - let(:project) { create(:project, :public) } + set(:user) { create(:user) } + set(:project) { create(:project, :public, :repository) } let(:feature) { ProjectFeature::DISABLED } before do stub_not_protect_default_branch project.add_developer(user) - project.project_feature.update( - builds_access_level: feature) + project.project_feature.update(builds_access_level: feature) sign_in(user) end describe 'GET index.json' do before do - create(:ci_empty_pipeline, status: 'pending', project: project) - create(:ci_empty_pipeline, status: 'running', project: project) - create(:ci_empty_pipeline, status: 'created', project: project) - create(:ci_empty_pipeline, status: 'success', project: project) + branch_head = project.commit + parent = branch_head.parent - get :index, namespace_id: project.namespace, - project_id: project, - format: :json + create(:ci_empty_pipeline, status: 'pending', project: project, sha: branch_head.id) + create(:ci_empty_pipeline, status: 'running', project: project, sha: branch_head.id) + create(:ci_empty_pipeline, status: 'created', project: project, sha: parent.id) + create(:ci_empty_pipeline, status: 'success', project: project, sha: parent.id) + end + + subject do + get :index, namespace_id: project.namespace, project_id: project, format: :json end it 'returns JSON with serialized pipelines' do - expect(response).to have_http_status(:ok) + subject + + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline') expect(json_response).to include('pipelines') @@ -39,6 +43,12 @@ describe Projects::PipelinesController do expect(json_response['count']['pending']).to eq 1 expect(json_response['count']['finished']).to eq 1 end + + context 'when performing gitaly calls', :request_store do + it 'limits the Gitaly requests' do + expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(8) + end + end end describe 'GET show JSON' do @@ -47,7 +57,7 @@ describe Projects::PipelinesController do it 'returns the pipeline' do get_pipeline_json - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response).not_to be_an(Array) expect(json_response['id']).to be(pipeline.id) expect(json_response['details']).to have_key 'stages' @@ -101,7 +111,7 @@ describe Projects::PipelinesController do end it 'returns html source for stage dropdown' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to render_template('projects/pipelines/_stage') expect(json_response).to include('html') end @@ -113,7 +123,7 @@ describe Projects::PipelinesController do end it 'responds with not found' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -138,11 +148,11 @@ describe Projects::PipelinesController do end it 'return a detailed pipeline status in json' do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(json_response['text']).to eq status.text expect(json_response['label']).to eq status.label expect(json_response['icon']).to eq status.icon - expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico" + expect(json_response['favicon']).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") end end @@ -161,14 +171,14 @@ describe Projects::PipelinesController do let(:feature) { ProjectFeature::ENABLED } it 'retries a pipeline without returning any content' do - expect(response).to have_http_status(:no_content) + expect(response).to have_gitlab_http_status(:no_content) expect(build.reload).to be_retried end end context 'when builds are disabled' do it 'fails to retry pipeline' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -188,14 +198,14 @@ describe Projects::PipelinesController do let(:feature) { ProjectFeature::ENABLED } it 'cancels a pipeline without returning any content' do - expect(response).to have_http_status(:no_content) + expect(response).to have_gitlab_http_status(:no_content) expect(pipeline.reload).to be_canceled end end context 'when builds are disabled' do it 'fails to retry pipeline' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb index ee46ad00947..21b6a6d45f5 100644 --- a/spec/controllers/projects/pipelines_settings_controller_spec.rb +++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb @@ -25,7 +25,7 @@ describe Projects::PipelinesSettingsController do let(:params) { { enabled: '', domain: 'mepmep.md' } } it 'redirects to the settings page' do - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.") end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 3cb1bec5ea2..a34dc27a5ed 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::ProjectMembersController 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(200) + expect(response).to have_gitlab_http_status(200) end end @@ -30,7 +30,7 @@ describe Projects::ProjectMembersController do user_ids: project_user.id, access_level: Gitlab::Access::GUEST - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(project.users).not_to include project_user end end @@ -79,7 +79,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -94,7 +94,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(project.members).to include member end end @@ -137,7 +137,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -168,7 +168,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -221,7 +221,7 @@ describe Projects::ProjectMembersController do project_id: project, id: 42 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -236,7 +236,7 @@ describe Projects::ProjectMembersController do project_id: project, id: member - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(project.members).not_to include member end end diff --git a/spec/controllers/projects/prometheus_controller_spec.rb b/spec/controllers/projects/prometheus_controller_spec.rb index 8407a53272a..bbfe78d305a 100644 --- a/spec/controllers/projects/prometheus_controller_spec.rb +++ b/spec/controllers/projects/prometheus_controller_spec.rb @@ -24,7 +24,7 @@ describe Projects::PrometheusController do it 'returns no content response' do get :active_metrics, project_params(format: :json) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end end @@ -38,7 +38,7 @@ describe Projects::PrometheusController do it 'returns no content response' do get :active_metrics, project_params(format: :json) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to eq(sample_response.deep_stringify_keys) end end @@ -47,7 +47,7 @@ describe Projects::PrometheusController do it 'returns not found response' do get :active_metrics, project_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index b4eaab29fed..3a0c3faa7b4 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::RawController do project_id: public_project, id: id) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Disposition']) .to eq('inline') @@ -30,7 +30,7 @@ describe Projects::RawController do project_id: public_project, id: id) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') end @@ -59,7 +59,7 @@ describe Projects::RawController do project_id: public_project, id: id) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -70,7 +70,7 @@ describe Projects::RawController do project_id: public_project, id: id) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -86,7 +86,7 @@ describe Projects::RawController do project_id: public_project, id: id) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header['Content-Disposition']) .to eq('inline') diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 2805968dcd9..17769a14def 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -35,13 +35,20 @@ describe Projects::Registry::RepositoriesController do it 'successfully renders container repositories' do go_to_index - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end it 'creates a root container repository' do expect { go_to_index }.to change { ContainerRepository.all.count }.by(1) expect(ContainerRepository.first).to be_root_repository end + + it 'json has a list of projects' do + go_to_index(format: :json) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end end context 'when there are no tags for this repository' do @@ -52,12 +59,37 @@ describe Projects::Registry::RepositoriesController do it 'successfully renders container repositories' do go_to_index - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end it 'does not ensure root container repository' do expect { go_to_index }.not_to change { ContainerRepository.all.count } end + + it 'responds with json if asked' do + go_to_index(format: :json) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_kind_of(Array) + end + end + end + end + + describe 'DELETE destroy' do + context 'when root container repository exists' do + let!(:repository) do + create(:container_repository, :root, project: project) + end + + before do + stub_container_registry_tags(repository: :any, tags: []) + end + + it 'deletes a repository' do + expect { delete_repository(repository) }.to change { ContainerRepository.all.count }.by(-1) + + expect(response).to have_gitlab_http_status(:no_content) end end end @@ -68,7 +100,7 @@ describe Projects::Registry::RepositoriesController do it 'responds with 404' do go_to_index - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end it 'does not ensure root container repository' do @@ -77,8 +109,16 @@ describe Projects::Registry::RepositoriesController do end end - def go_to_index + def go_to_index(format: :html) get :index, namespace_id: project.namespace, - project_id: project + project_id: project, + format: format + end + + def delete_repository(repository) + delete :destroy, namespace_id: project.namespace, + project_id: project, + id: repository, + format: :json end end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb index f4af3587d23..7fee8fd44ff 100644 --- a/spec/controllers/projects/registry/tags_controller_spec.rb +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -4,24 +4,83 @@ describe Projects::Registry::TagsController do let(:user) { create(:user) } let(:project) { create(:project, :private) } + let(:repository) do + create(:container_repository, name: 'image', project: project) + end + before do sign_in(user) stub_container_registry_config(enabled: true) end - context 'when user has access to registry' do + describe 'GET index' do + let(:tags) do + Array.new(40) { |i| "tag#{i}" } + end + before do - project.add_developer(user) + stub_container_registry_tags(repository: /image/, tags: tags) end - describe 'POST destroy' do + context 'when user can control the registry' do + before do + project.add_developer(user) + end + + it 'receive a list of tags' do + get_tags + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/tags') + expect(response).to include_pagination_headers + end + end + + context 'when user can read the registry' do + before do + project.add_reporter(user) + end + + it 'receive a list of tags' do + get_tags + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/tags') + expect(response).to include_pagination_headers + end + end + + context 'when user does not have access to registry' do + before do + project.add_guest(user) + end + + it 'does not receive a list of tags' do + get_tags + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + private + + def get_tags + get :index, namespace_id: project.namespace, + project_id: project, + repository_id: repository, + format: :json + end + end + + describe 'POST destroy' do + context 'when user has access to registry' do + before do + project.add_developer(user) + end + 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) + stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.]) end it 'makes it possible to delete regular tag' do @@ -37,12 +96,15 @@ describe Projects::Registry::TagsController do end end end - end - def destroy_tag(name) - post :destroy, namespace_id: project.namespace, - project_id: project, - repository_id: repository, - id: name + private + + def destroy_tag(name) + post :destroy, namespace_id: project.namespace, + project_id: project, + repository_id: repository, + id: name, + format: :json + end end end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index f712d1e0d63..8b777eb68ca 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -35,7 +35,7 @@ describe Projects::RepositoriesController do it "renders Not Found" do get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb index 2b6f988fd9c..89a13f3c976 100644 --- a/spec/controllers/projects/runners_controller_spec.rb +++ b/spec/controllers/projects/runners_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.description).to eq(new_desc) end end @@ -38,7 +38,7 @@ describe Projects::RunnersController do it 'destroys the runner' do delete :destroy, params - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(Ci::Runner.find_by(id: runner.id)).to be_nil end end @@ -53,7 +53,7 @@ describe Projects::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.active).to eq(true) end end @@ -68,7 +68,7 @@ describe Projects::RunnersController do runner.reload - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(runner.active).to eq(false) end end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index efba9cc7306..a907da2b60f 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::ServicesController do put :test, namespace_id: project.namespace, project_id: project, id: service.to_param - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index a8f4b79b64c..b8fe0f46f57 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Settings::CiCdController 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 have_gitlab_http_status(200) expect(response).to render_template(:show) end end diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index e0f9a5b24a6..3068837f394 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Settings::IntegrationsController 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 have_gitlab_http_status(200) expect(response).to render_template(:show) end end diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb index f73471f8ca8..3a4014b7768 100644 --- a/spec/controllers/projects/settings/repository_controller_spec.rb +++ b/spec/controllers/projects/settings/repository_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Settings::RepositoryController 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 have_gitlab_http_status(200) expect(response).to render_template(:show) end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 3a1550aa730..e7c0b484ede 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::SnippetsController do project_id: project, page: last_page.to_param expect(assigns(:snippets).current_page).to eq(last_page) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -41,7 +41,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace, project_id: project expect(assigns(:snippets)).not_to include(project_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -54,7 +54,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace, project_id: project expect(assigns(:snippets)).to include(project_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -67,7 +67,7 @@ describe Projects::SnippetsController do get :index, namespace_id: project.namespace, project_id: project expect(assigns(:snippets)).to include(project_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -316,7 +316,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -329,7 +329,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -342,7 +342,7 @@ describe Projects::SnippetsController do get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param expect(assigns(:snippet)).to eq(project_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -352,7 +352,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace, project_id: project, id: 42 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -364,7 +364,7 @@ describe Projects::SnippetsController do it 'responds with status 404' do get action, namespace_id: project.namespace, project_id: project, id: 42 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index 41d211ed1bb..4622e27e60f 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -28,13 +28,13 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns todo path and pending count' do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['count']).to eq 1 expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/) end @@ -47,7 +47,7 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(0) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not create todo for issue when user not logged in' do @@ -55,7 +55,7 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(0) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -68,7 +68,7 @@ describe Projects::TodosController do it "doesn't create todo" do expect { go }.not_to change { user.todos.count } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -96,13 +96,13 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns todo path and pending count' do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['count']).to eq 1 expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/) end @@ -115,7 +115,7 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(0) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not create todo for merge request user has no access to' do @@ -123,7 +123,7 @@ describe Projects::TodosController do go end.to change { user.todos.count }.by(0) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -136,7 +136,7 @@ describe Projects::TodosController do it "doesn't create todo" do expect { go }.not_to change { user.todos.count } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index 775f3998f5d..65b821c9486 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -64,7 +64,7 @@ describe Projects::TreeController do context "valid SHA commit ID with path" do let(:id) { '6d39438/.gitignore' } - it { expect(response).to have_http_status(302) } + it { expect(response).to have_gitlab_http_status(302) } end end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 488bcf31371..c2550b1efa7 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -18,7 +18,7 @@ describe Projects::UploadsController do namespace_id: project.namespace.to_param, project_id: project, format: :json - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -90,7 +90,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -98,7 +98,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -117,7 +117,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -125,7 +125,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -151,7 +151,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -192,7 +192,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -200,7 +200,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -220,7 +220,7 @@ describe Projects::UploadsController do it "responds with status 200" do go - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -228,7 +228,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -237,7 +237,7 @@ describe Projects::UploadsController do it "responds with status 404" do go - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index 6957fb43c19..d065cd00d00 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -50,7 +50,7 @@ describe Projects::VariablesController do post :update, namespace_id: project.namespace.to_param, project_id: project, id: variable.id, variable: { key: '?', value: variable.value } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template :show end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4459e227fb3..b1d7157e447 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1,6 +1,8 @@ require('spec_helper') describe ProjectsController do + include ProjectForksHelper + let(:project) { create(:project) } let(:public_project) { create(:project, :public) } let(:user) { create(:user) } @@ -22,7 +24,7 @@ describe ProjectsController do get :new, namespace_id: group.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('new') end end @@ -31,7 +33,7 @@ describe ProjectsController do it 'responds with status 404' do get :new, namespace_id: group.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(response).not_to render_template('new') end end @@ -139,8 +141,9 @@ describe ProjectsController do end end - context 'when the storage is not available', broken_storage: true do - let(:project) { create(:project, :broken_storage) } + context 'when the storage is not available', :broken_storage do + set(:project) { create(:project, :broken_storage) } + before do project.add_developer(user) sign_in(user) @@ -149,7 +152,7 @@ describe ProjectsController do it 'renders a 503' do get :show, namespace_id: project.namespace, id: project - expect(response).to have_http_status(503) + expect(response).to have_gitlab_http_status(503) end end @@ -219,6 +222,14 @@ describe ProjectsController do get :show, namespace_id: public_project.namespace, id: public_project expect(response).to render_template('_files') end + + it "renders the readme view" do + allow(controller).to receive(:current_user).and_return(user) + allow(user).to receive(:project_view).and_return('readme') + + get :show, namespace_id: public_project.namespace, id: public_project + expect(response).to render_template('_readme') + end end context "when the url contains .atom" do @@ -246,7 +257,7 @@ describe ProjectsController do get :show, namespace_id: project.namespace, id: project, format: :git - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(namespace_project_path) end end @@ -269,7 +280,7 @@ describe ProjectsController do expect(project.path).to include 'renamed_path' expect(assigns(:repository).path).to include project.path - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -285,7 +296,25 @@ describe ProjectsController do .not_to change { project.reload.path } expect(controller).to set_flash[:alert].to(/container registry tags/) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) + end + end + + it 'updates Fast Forward Merge attributes' do + controller.instance_variable_set(:@project, project) + + params = { + merge_method: :ff + } + + put :update, + namespace_id: project.namespace, + id: project.id, + project: params + + expect(response).to have_gitlab_http_status(302) + params.each do |param, value| + expect(project.public_send(param)).to eq(value) end end @@ -316,7 +345,7 @@ describe ProjectsController do project.reload expect(project.namespace).to eq(new_namespace) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when new namespace is empty' do @@ -335,7 +364,7 @@ describe ProjectsController do project.reload expect(project.namespace).to eq(old_namespace) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(flash[:alert]).to eq 'Please select a new namespace for your project.' end end @@ -352,16 +381,16 @@ describe ProjectsController do delete :destroy, namespace_id: project.namespace, id: project expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(dashboard_projects_path) end context "when the project is forked" do let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } + let(:forked_project) { fork_project(project, nil, repository: true) } let(:merge_request) do create(:merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end @@ -369,7 +398,7 @@ describe ProjectsController do project.merge_requests << merge_request sign_in(admin) - delete :destroy, namespace_id: fork_project.namespace, id: fork_project + delete :destroy, namespace_id: forked_project.namespace, id: forked_project expect(merge_request.reload.state).to eq('closed') end @@ -391,7 +420,7 @@ describe ProjectsController do end it 'has http status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'changes the user incoming email token' do @@ -436,18 +465,14 @@ describe ProjectsController do end context 'with forked project' do - let(:project_fork) { create(:project, :repository, namespace: user.namespace) } - - before do - create(:forked_project_link, forked_to_project: project_fork) - end + let(:forked_project) { fork_project(create(:project, :public), user) } it 'removes fork from project' do delete(:remove_fork, - namespace_id: project_fork.namespace.to_param, - id: project_fork.to_param, format: :js) + namespace_id: forked_project.namespace.to_param, + id: forked_project.to_param, format: :js) - expect(project_fork.forked?).to be_falsey + expect(forked_project.reload.forked?).to be_falsey expect(flash[:notice]).to eq('The fork relationship has been removed.') expect(response).to render_template(:remove_fork) end @@ -471,7 +496,7 @@ describe ProjectsController do delete(:remove_fork, namespace_id: project.namespace, id: project, format: :js) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -519,7 +544,7 @@ describe ProjectsController do get :show, namespace_id: public_project.namespace, id: public_project expect(assigns(:project)).to eq(public_project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -558,13 +583,13 @@ describe ProjectsController do it 'does not 404' do post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase - expect(response).not_to have_http_status(404) + expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -574,7 +599,7 @@ describe ProjectsController do it 'returns not found' do post :toggle_star, namespace_id: 'foo', id: 'bar' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -588,13 +613,13 @@ describe ProjectsController do it 'does not 404' do delete :destroy, namespace_id: project.namespace, id: project.path.upcase - expect(response).not_to have_http_status(404) + expect(response).not_to have_gitlab_http_status(404) end it 'does not redirect to the correct casing' do delete :destroy, namespace_id: project.namespace, id: project.path.upcase - expect(response).not_to have_http_status(301) + expect(response).not_to have_gitlab_http_status(301) end end @@ -604,7 +629,7 @@ describe ProjectsController do it 'returns not found' do delete :destroy, namespace_id: 'foo', id: 'bar' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -621,7 +646,7 @@ describe ProjectsController do it 'returns 302' do get :export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -633,7 +658,7 @@ describe ProjectsController do it 'returns 404' do get :export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -649,7 +674,7 @@ describe ProjectsController do it 'returns 302' do get :download_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -661,7 +686,7 @@ describe ProjectsController do it 'returns 404' do get :download_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -677,7 +702,7 @@ describe ProjectsController do it 'returns 302' do post :remove_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -689,7 +714,7 @@ describe ProjectsController do it 'returns 404' do post :remove_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -705,7 +730,7 @@ describe ProjectsController do it 'returns 302' do post :generate_new_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) end end @@ -717,7 +742,7 @@ describe ProjectsController do it 'returns 404' do post :generate_new_export, namespace_id: project.namespace, id: project - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 5a4ab39ab86..1d3ddfbd220 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -76,12 +76,68 @@ describe RegistrationsController do sign_in(user) end - it 'schedules the user for destruction' do - expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {}) + def expect_failure(message) + expect(flash[:alert]).to eq(message) + expect(response.status).to eq(303) + expect(response).to redirect_to profile_account_path + end + + def expect_password_failure + expect_failure('Invalid password') + end + + def expect_username_failure + expect_failure('Invalid username') + end + + def expect_success + expect(flash[:notice]).to eq 'Account scheduled for removal.' + expect(response.status).to eq(303) + expect(response).to redirect_to new_user_session_path + end - post(:destroy) + context 'user requires password confirmation' do + it 'fails if password confirmation is not provided' do + post :destroy - expect(response.status).to eq(302) + expect_password_failure + end + + it 'fails if password confirmation is wrong' do + post :destroy, password: 'wrong password' + + expect_password_failure + end + + it 'succeeds if password is confirmed' do + post :destroy, password: '12345678' + + expect_success + end + end + + context 'user does not require password confirmation' do + before do + stub_application_setting(password_authentication_enabled: false) + end + + it 'fails if username confirmation is not provided' do + post :destroy + + expect_username_failure + end + + it 'fails if username confirmation is wrong' do + post :destroy, username: 'wrong username' + + expect_username_failure + end + + it 'succeeds if username is confirmed' do + post :destroy, username: user.username + + expect_success + end end end end diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 31593ce7311..54a9af92f07 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -69,7 +69,7 @@ describe SentNotificationsController do end it 'returns a 404' do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index a22fd8eaf9b..55bd4352bd3 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -19,7 +19,7 @@ describe SessionsController do it 'redirects to :omniauth_authorize_path' do get(:new) - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to('/saml') end end @@ -28,7 +28,7 @@ describe SessionsController do it 'responds with 200' do get(:new, auto_sign_in: 'false') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb index 225753333ee..e6148ea1734 100644 --- a/spec/controllers/snippets/notes_controller_spec.rb +++ b/spec/controllers/snippets/notes_controller_spec.rb @@ -20,7 +20,7 @@ describe Snippets::NotesController do end it "returns status 200" do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns not empty array of notes" do @@ -37,7 +37,7 @@ describe Snippets::NotesController do it "returns status 404" do get :index, { snippet_id: internal_snippet } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -49,7 +49,7 @@ describe Snippets::NotesController do it "returns status 200" do get :index, { snippet_id: internal_snippet } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -63,7 +63,7 @@ describe Snippets::NotesController do it "returns status 404" do get :index, { snippet_id: private_snippet } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -75,7 +75,7 @@ describe Snippets::NotesController do it "returns status 404" do get :index, { snippet_id: private_snippet } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -89,7 +89,7 @@ describe Snippets::NotesController do it "returns status 200" do get :index, { snippet_id: private_snippet } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns 1 note" do @@ -134,7 +134,7 @@ describe Snippets::NotesController do it "returns status 200" do delete :destroy, request_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "deletes the note" do @@ -162,7 +162,7 @@ describe Snippets::NotesController do it "returns status 404" do delete :destroy, request_params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not update the note" do @@ -182,7 +182,7 @@ describe Snippets::NotesController do it "toggles the award emoji" do expect { subject }.to change { note.award_emoji.count }.by(1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "removes the already awarded emoji when it exists" do @@ -190,7 +190,7 @@ describe Snippets::NotesController do expect { subject }.to change { AwardEmoji.count }.by(-1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index be273acb69b..9effe47ab05 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -40,7 +40,7 @@ describe SnippetsController do it 'responds with status 200' do get :new - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -69,7 +69,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: other_personal_snippet.to_param - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -78,7 +78,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -104,7 +104,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -129,7 +129,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -138,7 +138,7 @@ describe SnippetsController do get :show, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -152,7 +152,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -432,7 +432,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: other_personal_snippet.to_param - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -443,7 +443,7 @@ describe SnippetsController do it 'responds with status 200' do expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'has expected headers' do @@ -475,7 +475,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -500,7 +500,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'CRLF line ending' do @@ -527,7 +527,7 @@ describe SnippetsController do get :raw, id: personal_snippet.to_param expect(assigns(:snippet)).to eq(personal_snippet) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -541,7 +541,7 @@ describe SnippetsController do it 'responds with status 404' do get :raw, id: 'doesntexist' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index b29f3d861be..7e42e43345c 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -18,7 +18,7 @@ describe UploadsController do it "returns 401 when the user is not logged in" do post :create, model: model, id: snippet.id, format: :json - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns 404 when user can't comment on a snippet" do @@ -27,7 +27,7 @@ describe UploadsController do sign_in(user) post :create, model: model, id: private_snippet.id, format: :json - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -39,7 +39,7 @@ describe UploadsController do it "returns an error without file" do post :create, model: model, id: snippet.id, format: :json - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end it "returns an error with invalid model" do @@ -50,7 +50,7 @@ describe UploadsController do it "returns 404 status when object not found" do post :create, model: model, id: 9999, format: :json - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'with valid image' do @@ -174,7 +174,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -190,7 +190,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -214,7 +214,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -233,7 +233,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -285,7 +285,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -301,7 +301,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -316,7 +316,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -335,7 +335,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -378,7 +378,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -394,7 +394,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -414,7 +414,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -433,7 +433,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -485,7 +485,7 @@ describe UploadsController do it "responds with status 200" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -501,7 +501,7 @@ describe UploadsController do it "responds with status 404" do get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -516,7 +516,7 @@ describe UploadsController do it 'responds with status 200' do get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do @@ -535,7 +535,7 @@ describe UploadsController do it 'responds with status 200' do get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it_behaves_like 'content not cached without revalidation' do diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 2cecd2646fc..01ab59aa363 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -24,7 +24,7 @@ describe UsersController do it 'renders the show template' do get :show, username: user.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end end @@ -49,7 +49,7 @@ describe UsersController do it 'renders show' do get :show, username: user.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end end @@ -70,7 +70,7 @@ describe UsersController do it 'renders 404' do get :show, username: 'nonexistent' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -82,7 +82,7 @@ describe UsersController do get :calendar, username: user.username, format: :json - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'forked project' do @@ -139,7 +139,7 @@ describe UsersController do context 'format html' do it 'renders snippets page' do get :snippets, username: user.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to render_template('show') end end @@ -147,7 +147,7 @@ describe UsersController do context 'format json' do it 'response with snippets json data' do get :snippets, username: user.username, format: :json - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(JSON.parse(response.body)).to have_key('html') end end diff --git a/spec/factories/ci/build_trace_section_names.rb b/spec/factories/ci/build_trace_section_names.rb new file mode 100644 index 00000000000..1c16225f0e5 --- /dev/null +++ b/spec/factories/ci/build_trace_section_names.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :ci_build_trace_section_name, class: Ci::BuildTraceSectionName do + sequence(:name) { |n| "section_#{n}" } + project factory: :project + end +end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c2b59239af9..cf38066dedc 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -119,7 +119,7 @@ FactoryGirl.define do finished_at nil end - factory :ci_build_tag do + trait :tag do tag true end diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index e5ea6b41ea3..f994c2df821 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -47,6 +47,7 @@ FactoryGirl.define do trait :invalid do config(rspec: nil) + failure_reason :config_error end trait :blocked do diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index e5abfd67d60..0dd1238d6e2 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -12,7 +12,7 @@ FactoryGirl.define do deployment.project ||= deployment.environment.project unless deployment.project.repository_exists? - allow(deployment.project.repository).to receive(:fetch_ref) + allow(deployment.project.repository).to receive(:create_ref) end end end diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb index 8303861bcfe..c9ab87a15aa 100644 --- a/spec/factories/emails.rb +++ b/spec/factories/emails.rb @@ -2,5 +2,7 @@ FactoryGirl.define do factory :email do user email { generate(:email_alias) } + + trait(:confirmed) { confirmed_at Time.now } end end diff --git a/spec/factories/fork_networks.rb b/spec/factories/fork_networks.rb new file mode 100644 index 00000000000..f42d36f3d19 --- /dev/null +++ b/spec/factories/fork_networks.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :fork_network do + association :root_project, factory: :project + end +end diff --git a/spec/factories/gcp/cluster.rb b/spec/factories/gcp/cluster.rb new file mode 100644 index 00000000000..630e40da888 --- /dev/null +++ b/spec/factories/gcp/cluster.rb @@ -0,0 +1,38 @@ +FactoryGirl.define do + factory :gcp_cluster, class: Gcp::Cluster do + project + user + enabled true + gcp_project_id 'gcp-project-12345' + gcp_cluster_name 'test-cluster' + gcp_cluster_zone 'us-central1-a' + gcp_cluster_size 1 + gcp_machine_type 'n1-standard-4' + + trait :with_kubernetes_service do + after(:create) do |cluster, evaluator| + create(:kubernetes_service, project: cluster.project).tap do |service| + cluster.update(service: service) + end + end + end + + trait :custom_project_namespace do + project_namespace 'sample-app' + end + + trait :created_on_gke do + status_event :make_created + endpoint '111.111.111.111' + ca_cert 'xxxxxx' + kubernetes_token 'xxxxxx' + username 'xxxxxx' + password 'xxxxxx' + end + + trait :errored do + status_event :make_errored + status_reason 'general error' + end + end +end diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb new file mode 100644 index 00000000000..e7966cee77b --- /dev/null +++ b/spec/factories/gitaly/commit.rb @@ -0,0 +1,17 @@ +FactoryGirl.define do + sequence(:gitaly_commit_id) { Digest::SHA1.hexdigest(Time.now.to_f.to_s) } + + factory :gitaly_commit, class: Gitaly::GitCommit do + skip_create + + id { generate(:gitaly_commit_id) } + parent_ids do + ids = [generate(:gitaly_commit_id), generate(:gitaly_commit_id)] + Google::Protobuf::RepeatedField.new(:string, ids) + end + subject { "My commit" } + body { subject + "\nMy body" } + author { build(:gitaly_commit_author) } + committer { build(:gitaly_commit_author) } + end +end diff --git a/spec/factories/gitaly/commit_author.rb b/spec/factories/gitaly/commit_author.rb new file mode 100644 index 00000000000..341873a2002 --- /dev/null +++ b/spec/factories/gitaly/commit_author.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :gitaly_commit_author, class: Gitaly::CommitAuthor do + skip_create + + name { generate(:name) } + email { generate(:email) } + date { Google::Protobuf::Timestamp.new(seconds: Time.now.to_i) } + end +end diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb new file mode 100644 index 00000000000..66ecb44d84b --- /dev/null +++ b/spec/factories/gpg_key_subkeys.rb @@ -0,0 +1,10 @@ +require_relative '../support/gpg_helpers' + +FactoryGirl.define do + factory :gpg_key_subkey do + gpg_key + + sequence(:keyid) { |n| "keyid-#{n}" } + sequence(:fingerprint) { |n| "fingerprint-#{n}" } + end +end diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index 1258dce8940..93218e5763e 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -4,5 +4,9 @@ FactoryGirl.define do factory :gpg_key do key GpgHelpers::User1.public_key user + + factory :gpg_key_with_subkeys do + key GpgHelpers::User1.public_key_with_extra_signing_key + end end end diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb index c0beecf0bea..e9798ff6a41 100644 --- a/spec/factories/gpg_signature.rb +++ b/spec/factories/gpg_signature.rb @@ -5,7 +5,7 @@ FactoryGirl.define do commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } project gpg_key - gpg_key_primary_keyid { gpg_key.primary_keyid } + gpg_key_primary_keyid { gpg_key.keyid } verification_status :verified end end diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb new file mode 100644 index 00000000000..406c7c3caf1 --- /dev/null +++ b/spec/factories/instance_configuration.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :instance_configuration do + skip_create + end +end diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index cbec716d6ea..7c4a22c94c2 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -22,6 +22,11 @@ FactoryGirl.define do trait :with_diffs do end + trait :with_image_diffs do + source_branch "add_images_and_changes" + target_branch "master" + end + trait :without_diffs do source_branch "improve/awesome" target_branch "master" @@ -68,6 +73,12 @@ FactoryGirl.define do merge_user author end + trait :remove_source_branch do + merge_params do + { 'force_remove_source_branch' => '1' } + end + end + after(:build) do |merge_request| target_project = merge_request.target_project source_project = merge_request.source_project diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 7493b0a8b35..4034e7905ad 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -143,6 +143,16 @@ FactoryGirl.define do end end + trait :wiki_repo do + after(:create) do |project| + raise 'Failed to create wiki repository!' unless project.create_wiki + end + end + + trait :read_only do + repository_read_only true + end + trait :broken_repo do after(:create) do |project| raise "Failed to create repository!" unless project.create_repository diff --git a/spec/factories/services.rb b/spec/factories/services.rb index c2674ce2d11..ccf63f3ffa4 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -38,6 +38,8 @@ FactoryGirl.define do active true properties( url: 'https://jira.example.com', + username: 'jira_user', + password: 'my-secret-password', project_key: 'jira-key' ) end diff --git a/spec/factories/user_custom_attributes.rb b/spec/factories/user_custom_attributes.rb new file mode 100644 index 00000000000..278cf290d4f --- /dev/null +++ b/spec/factories/user_custom_attributes.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :user_custom_attribute do + user + sequence(:key) { |n| "key#{n}" } + sequence(:value) { |n| "value#{n}" } + end +end diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 2144f6ba635..766cd4b0090 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Admin::AbuseReports", js: true do +describe "Admin::AbuseReports", :js do let(:user) { create(:user) } context 'as an admin' do diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb index cbccf370514..9cb351282a0 100644 --- a/spec/features/admin/admin_broadcast_messages_spec.rb +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -40,7 +40,7 @@ feature 'Admin Broadcast Messages' do expect(page).not_to have_content 'Migration to new server' end - scenario 'Live preview a customized broadcast message', js: true do + scenario 'Live preview a customized broadcast message', :js do fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" page.within('.broadcast-message-preview') do diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb index e214ae6b78d..2abdd3c9ef2 100644 --- a/spec/features/admin/admin_disables_two_factor_spec.rb +++ b/spec/features/admin/admin_disables_two_factor_spec.rb @@ -1,13 +1,13 @@ require 'rails_helper' feature 'Admin disables 2FA for a user' do - scenario 'successfully', js: true do + scenario 'successfully', :js do sign_in(create(:admin)) user = create(:user, :two_factor) edit_user(user) page.within('.two-factor-status') do - click_link 'Disable' + accept_confirm { click_link 'Disable' } end page.within('.two-factor-status') do diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index 3768727d8ae..a5f22848031 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -52,7 +52,7 @@ feature 'Admin Groups' do expect_selected_visibility(internal) end - scenario 'when entered in group path, it auto filled the group name', js: true do + scenario 'when entered in group path, it auto filled the group name', :js do visit admin_groups_path click_link "New group" group_path = 'gitlab' @@ -81,7 +81,7 @@ feature 'Admin Groups' do expect_selected_visibility(group.visibility_level) end - scenario 'edit group path does not change group name', js: true do + scenario 'edit group path does not change group name', :js do group = create(:group, :private) visit admin_group_edit_path(group) @@ -93,7 +93,7 @@ feature 'Admin Groups' do end end - describe 'add user into a group', js: true do + describe 'add user into a group', :js do shared_context 'adds user into a group' do it do visit admin_group_path(group) @@ -124,7 +124,7 @@ feature 'Admin Groups' do group.add_user(:user, Gitlab::Access::OWNER) end - it 'adds admin a to a group as developer', js: true do + it 'adds admin a to a group as developer', :js do visit group_group_members_path(group) page.within '.users-group-form' do @@ -141,7 +141,7 @@ feature 'Admin Groups' do end end - describe 'admin remove himself from a group', js: true do + describe 'admin remove himself from a group', :js do it 'removes admin from the group' do group.add_user(current_user, Gitlab::Access::DEVELOPER) @@ -152,7 +152,7 @@ feature 'Admin Groups' do expect(page).to have_content('Developer') end - find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click + accept_confirm { find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click } visit group_group_members_path(group) diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index 37fd3e171eb..4430fc15501 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature "Admin Health Check", feature: true, broken_storage: true do +feature "Admin Health Check", :feature, :broken_storage do include StubENV before do @@ -65,9 +65,11 @@ feature "Admin Health Check", feature: true, broken_storage: true do it 'shows storage failure information' do hostname = Gitlab::Environment.hostname + maximum_failures = Gitlab::CurrentSettings.current_application_settings + .circuitbreaker_failure_count_threshold expect(page).to have_content('broken: failed storage access attempt on host:') - expect(page).to have_content("#{hostname}: 1 of 10 failures.") + expect(page).to have_content("#{hostname}: 1 of #{maximum_failures} failures.") end it 'allows resetting storage failures' do diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 91f08dbad5d..eec44549a03 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -62,19 +62,19 @@ describe 'Admin::Hooks', :js do it 'from hooks list page' do visit admin_hooks_path - expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1) + expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1) end it 'from hook edit page' do visit admin_hooks_path click_link 'Edit' - expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1) + expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1) end end end - describe 'Test', js: true do + describe 'Test', :js do before do WebMock.stub_request(:post, @system_hook.url) visit admin_hooks_path diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index ae9b47299e6..de406d7d966 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -30,10 +30,10 @@ RSpec.describe 'admin issues labels' do end end - it 'deletes all labels', js: true do + it 'deletes all labels', :js do page.within '.labels' do page.all('.btn-remove').each do |remove| - remove.click + accept_confirm { remove.click } wait_for_requests end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index f4f2505d436..94b12105088 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -28,7 +28,7 @@ describe "Admin::Projects" do expect(page).not_to have_content(archived_project.name) end - it 'renders all projects', js: true do + it 'renders all projects', :js do find(:css, '#sort-projects-dropdown').click click_link 'Show archived projects' @@ -37,7 +37,7 @@ describe "Admin::Projects" do expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') end - it 'renders only archived projects', js: true do + it 'renders only archived projects', :js do find(:css, '#sort-projects-dropdown').click click_link 'Show archived projects only' @@ -74,7 +74,7 @@ describe "Admin::Projects" do .to receive(:move_uploads_to_new_namespace).and_return(true) end - it 'transfers project to group web', js: true do + it 'transfers project to group web', :js do visit admin_project_path(project) click_button 'Search for Namespace' @@ -91,7 +91,7 @@ describe "Admin::Projects" do project.team << [user, :master] end - it 'adds admin a to a project as developer', js: true do + it 'adds admin a to a project as developer', :js do visit project_project_members_path(project) page.within '.users-project-form' do diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index c490dce7ab0..1218ea52227 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -73,7 +73,7 @@ feature 'Admin updates settings' do context 'sign-in restrictions', :js do it 'de-activates oauth sign-in source' do - find('.btn', text: 'GitLab.com').click + find('input#application_setting_enabled_oauth_sign_in_sources_[value=gitlab]').send_keys(:return) expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active') end @@ -95,6 +95,29 @@ feature 'Admin updates settings' do expect(find_field('ED25519 SSH keys').value).to eq(forbidden) end + scenario 'Change Performance Bar settings' do + group = create(:group) + + check 'Enable the Performance Bar' + fill_in 'Allowed group', with: group.path + + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + expect(find_field('Enable the Performance Bar')).to be_checked + expect(find_field('Allowed group').value).to eq group.path + + uncheck 'Enable the Performance Bar' + + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + expect(find_field('Enable the Performance Bar')).not_to be_checked + expect(find_field('Allowed group').value).to be_nil + end + def check_all_events page.check('Active') page.check('Push') diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index 034682dae27..e16eae219a4 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Admin > Users > Impersonation Tokens', js: true do +describe 'Admin > Users > Impersonation Tokens', :js do let(:admin) { create(:admin) } let!(:user) { create(:user) } @@ -24,7 +24,7 @@ describe 'Admin > Users > Impersonation Tokens', js: true do fill_in "Name", with: name # Set date to 1st of next month - find_field("Expires at").trigger('focus') + find_field("Expires at").click find(".pika-next").click click_on "1" @@ -60,7 +60,7 @@ describe 'Admin > Users > Impersonation Tokens', js: true do it "allows revocation of an active impersonation token" do visit admin_user_impersonation_tokens_path(user_id: user.username) - click_on "Revoke" + accept_confirm { click_on "Revoke" } expect(page).to have_selector(".settings-message") expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.") diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index e2e2b13cf8a..b47f9055d29 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -288,9 +288,9 @@ describe "Admin::Users" do end end - it 'allows group membership to be revoked', js: true do + it 'allows group membership to be revoked', :js do page.within(first('.group_member')) do - find('.btn-remove').click + accept_confirm { find('.btn-remove').click } end wait_for_requests @@ -309,7 +309,7 @@ describe "Admin::Users" do end end - describe 'remove users secondary email', js: true do + describe 'remove users secondary email', :js do let!(:secondary_email) do create :email, email: 'secondary@example.com', user: user end @@ -319,7 +319,7 @@ describe "Admin::Users" do expect(page).to have_content("Secondary email: #{secondary_email.email}") - find("#remove_email_#{secondary_email.id}").click + accept_confirm { find("#remove_email_#{secondary_email.id}").click } expect(page).not_to have_content(secondary_email.email) end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index c2b7543a690..f1ac73ff819 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -32,12 +32,12 @@ feature 'Admin uses repository checks' do end end - scenario 'to clear all repository checks', js: true do + scenario 'to clear all repository checks', :js do visit admin_application_settings_path expect(RepositoryCheck::ClearWorker).to receive(:perform_async) - click_link 'Clear all repository checks' + accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } expect(page).to have_content('Started asynchronous removal of all repository check states.') end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index 5aae2dbaf91..89c9d377003 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -13,8 +13,10 @@ describe "Dashboard Issues Feed" do end describe "atom feed" do - it "renders atom feed via private token" do - visit issues_dashboard_path(:atom, private_token: user.private_token) + it "renders atom feed via personal access token" do + personal_access_token = create(:personal_access_token, user: user) + + visit issues_dashboard_path(:atom, private_token: personal_access_token.token) expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{user.name} issues") diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index 321c8a2a670..2c0c331b6db 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -4,9 +4,11 @@ describe "Dashboard Feed" do describe "GET /" do let!(:user) { create(:user, name: "Jonh") } - context "projects atom feed via private token" do + context "projects atom feed via personal access token" do it "renders projects atom feed" do - visit dashboard_projects_path(:atom, private_token: user.private_token) + personal_access_token = create(:personal_access_token, user: user) + + visit dashboard_projects_path(:atom, private_token: personal_access_token.token) expect(body).to have_selector('feed title') end end diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 3eeb4d35131..4102ac0588a 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -28,10 +28,12 @@ describe 'Issues Feed' do end end - context 'when authenticated via private token' do + context 'when authenticated via personal access token' do it 'renders atom feed' do + personal_access_token = create(:personal_access_token, user: user) + visit project_issues_path(project, :atom, - private_token: user.private_token) + private_token: personal_access_token.token) expect(response_headers['Content-Type']) .to have_content('application/atom+xml') diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 9ce687afb31..2b934d81674 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -4,9 +4,11 @@ describe "User Feed" do describe "GET /" do let!(:user) { create(:user) } - context 'user atom feed via private token' do + context 'user atom feed via personal access token' do it "renders user atom feed" do - visit user_path(user, :atom, private_token: user.private_token) + personal_access_token = create(:personal_access_token, user: user) + + visit user_path(user, :atom, private_token: personal_access_token.token) expect(body).to have_selector('feed title') end end diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index dff6f96b663..4a7c3e4f1ab 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -31,7 +31,7 @@ describe 'Auto deploy' do expect(page).to have_link('Set up auto deploy') end - it 'includes OpenShift as an available template', js: true do + it 'includes OpenShift as an available template', :js do click_link 'Set up auto deploy' click_button 'Apply a GitLab CI Yaml template' @@ -40,7 +40,7 @@ describe 'Auto deploy' do end end - it 'creates a merge request using "auto-deploy" branch', js: true do + it 'creates a merge request using "auto-deploy" branch', :js do click_link 'Set up auto deploy' click_button 'Apply a GitLab CI Yaml template' within '.gitlab-ci-yml-selector' do diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index c480b5b7e34..e4cfcea45a5 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -101,7 +101,7 @@ describe 'Issue Boards add issue modal', :js do click_button 'Cancel' end - first('.board-delete').click + accept_confirm { first('.board-delete').click } click_button('Add issues') diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 33aca6cb527..e8d779f5772 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -1,7 +1,8 @@ require 'rails_helper' -describe 'Issue Boards', js: true do +describe 'Issue Boards', :js do include DragTo + include MobileHelpers let(:group) { create(:group, :nested) } let(:project) { create(:project, :public, namespace: group) } @@ -13,7 +14,7 @@ describe 'Issue Boards', js: true do project.team << [user, :master] project.team << [user2, :master] - page.driver.set_cookie('sidebar_collapsed', 'true') + set_cookie('sidebar_collapsed', 'true') sign_in(user) end @@ -135,7 +136,7 @@ describe 'Issue Boards', js: true do it 'allows user to delete board' do page.within(find('.board:nth-child(2)')) do - find('.board-delete').click + accept_confirm { find('.board-delete').click } end wait_for_requests @@ -150,7 +151,7 @@ describe 'Issue Boards', js: true do find('.dropdown-menu-close').click page.within(find('.board:nth-child(2)')) do - find('.board-delete').click + accept_confirm { find('.board-delete').click } end wait_for_requests @@ -171,12 +172,14 @@ describe 'Issue Boards', js: true do expect(page).to have_selector('.card', count: 20) expect(page).to have_content('Showing 20 of 58 issues') + find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests expect(page).to have_selector('.card', count: 40) expect(page).to have_content('Showing 40 of 58 issues') + find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests @@ -377,7 +380,7 @@ describe 'Issue Boards', js: true do end it 'filters by milestone' do - set_filter("milestone", "\"#{milestone.title}\"") + set_filter("milestone", "\"#{milestone.title}") click_filter_link(milestone.title) submit_filter @@ -398,7 +401,7 @@ describe 'Issue Boards', js: true do end it 'filters by label with space after reload' do - set_filter("label", "\"#{accepting.title}\"") + set_filter("label", "\"#{accepting.title}") click_filter_link(accepting.title) submit_filter @@ -449,11 +452,13 @@ describe 'Issue Boards', js: true do expect(page).to have_selector('.card', count: 20) expect(page).to have_content('Showing 20 of 51 issues') + find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") expect(page).to have_selector('.card', count: 40) expect(page).to have_content('Showing 40 of 51 issues') + find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") expect(page).to have_selector('.card', count: 51) @@ -517,7 +522,7 @@ describe 'Issue Boards', js: true do end it 'allows user to use keyboard shortcuts' do - find('.boards-list').native.send_keys('i') + find('body').native.send_keys('i') expect(page).to have_content('New Issue') end end @@ -534,7 +539,7 @@ describe 'Issue Boards', js: true do end it 'does not show create new list' do - expect(page).not_to have_selector('.js-new-board-list') + expect(page).not_to have_button('.js-new-board-list') end it 'does not allow dragging' do @@ -559,6 +564,9 @@ describe 'Issue Boards', js: true do end def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0) + # ensure there is enough horizontal space for four boards + resize_window(2000, 800) + drag_to(selector: selector, scrollable: '#board-app', list_from_index: list_from_index, diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 61b53aa5d1e..435de3861cf 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Issue Boards shortcut', js: true do +describe 'Issue Boards shortcut', :js do let(:project) { create(:project) } before do diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index f67372337ec..5ac4d87e90b 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Issue Boards new issue', js: true do +describe 'Issue Boards new issue', :js do let(:project) { create(:project, :public) } let(:board) { create(:board, project: project) } let!(:list) { create(:list, board: board, position: 0) } diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index c3bf50ef9d1..9137ab82ff4 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' -describe 'Issue Boards', js: true do +describe 'Issue Boards', :js do + include BoardHelpers + let(:user) { create(:user) } let(:user2) { create(:user) } let(:project) { create(:project, :public) } @@ -49,7 +51,7 @@ describe 'Issue Boards', js: true do expect(page).to have_selector('.issue-boards-sidebar') - find('.gutter-toggle').trigger('click') + find('.gutter-toggle').click expect(page).not_to have_selector('.issue-boards-sidebar') end @@ -169,7 +171,7 @@ describe 'Issue Boards', js: true do end page.within(find('.board:nth-child(2)')) do - find('.card:nth-child(2)').trigger('click') + find('.card:nth-child(2)').click end page.within('.assignee') do @@ -309,6 +311,21 @@ describe 'Issue Boards', js: true do expect(card).to have_selector('.label', count: 1) expect(card).not_to have_content(stretch.title) end + + it 'creates new label' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + click_link 'Create new label' + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + click_button 'Create' + wait_for_requests + + expect(page).to have_link 'test label' + end + end end context 'subscription' do @@ -322,19 +339,4 @@ describe 'Issue Boards', js: true do end end end - - def click_card(card) - page.within(card) do - first('.card-number').click - end - - wait_for_sidebar - end - - def wait_for_sidebar - # loop until the CSS transition is complete - Timeout.timeout(0.5) do - loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290 - end - end end diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index 4fc6956d111..a9530becb65 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -63,8 +63,8 @@ feature 'Contributions Calendar', :js do Event.create(note_comment_params) end - def selected_day_activities - find('.user-calendar-activities').text + def selected_day_activities(visible: true) + find('.user-calendar-activities', visible: visible).text end before do @@ -112,7 +112,7 @@ feature 'Contributions Calendar', :js do end it 'hides calendar day activities' do - expect(selected_day_activities).to be_empty + expect(selected_day_activities(visible: false)).to be_empty end end end diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb index af4cc00162a..9bc23baf6cf 100644 --- a/spec/features/ci_lint_spec.rb +++ b/spec/features/ci_lint_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'CI Lint', js: true do +describe 'CI Lint', :js do before do sign_in(create(:user)) end @@ -10,6 +10,7 @@ describe 'CI Lint', js: true do visit ci_lint_path # Ace editor updates a hidden textarea and it happens asynchronously # `sleep 0.1` is actually needed here because of this + find('#ci-editor') execute_script("ace.edit('ci-editor').setValue(" + yaml_content.to_json + ");") sleep 0.1 click_on 'Validate' diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index ae39ba4da6b..bef2aa9e0e5 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Container Registry" do +describe "Container Registry", :js do let(:user) { create(:user) } let(:project) { create(:project) } @@ -41,16 +41,19 @@ describe "Container Registry" do expect_any_instance_of(ContainerRepository) .to receive(:delete_tags!).and_return(true) - click_on 'Remove repository' + click_on(class: 'js-remove-repo') end scenario 'user removes a specific tag from container repository' do visit_container_registry + find('.js-toggle-repo').click + wait_for_requests + expect_any_instance_of(ContainerRegistry::Tag) .to receive(:delete).and_return(true) - click_on 'Remove tag' + click_on(class: 'js-delete-registry') end end diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index dfeba722ac6..c6ba1211b9e 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Copy as GFM', js: true do +describe 'Copy as GFM', :js do include MarkupHelper include RepoHelpers include ActionView::Helpers::JavaScriptHelper @@ -446,7 +446,7 @@ describe 'Copy as GFM', js: true do def verify(label, *gfms) aggregate_failures(label) do gfms.each do |gfm| - html = gfm_to_html(gfm) + html = gfm_to_html(gfm).gsub(/\A
|
\z/, '') output_gfm = html_to_gfm(html) expect(output_gfm.strip).to eq(gfm.strip) end @@ -463,42 +463,98 @@ describe 'Copy as GFM', js: true do let(:project) { create(:project, :repository) } context 'from a diff' do - before do - visit project_commit_path(project, sample_commit.id) - end + shared_examples 'copying code from a diff' do + context 'selecting one word of text' do + it 'copies as inline code' do + verify( + '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no', - context 'selecting one word of text' do - it 'copies as inline code' do - verify( - '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no', + '`RuntimeError`', - '`RuntimeError`' - ) + target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' + ) + end end - end - context 'selecting one line of text' do - it 'copies as inline code' do - verify( - '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line', + context 'selecting one line of text' do + it 'copies as inline code' do + verify( + '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]', - '`raise RuntimeError, "System commands must be given as an array of strings"`' - ) + '`raise RuntimeError, "System commands must be given as an array of strings"`', + + target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' + ) + end + end + + context 'selecting multiple lines of text' do + it 'copies as a code block' do + verify( + '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', + + <<-GFM.strip_heredoc, + ```ruby + raise RuntimeError, "System commands must be given as an array of strings" + end + ``` + GFM + + target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' + ) + end end end - context 'selecting multiple lines of text' do - it 'copies as a code block' do - verify( - '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', + context 'inline diff' do + before do + visit project_commit_path(project, sample_commit.id, view: 'inline') + end - <<-GFM.strip_heredoc, - ```ruby - raise RuntimeError, "System commands must be given as an array of strings" - end - ``` - GFM - ) + it_behaves_like 'copying code from a diff' + end + + context 'parallel diff' do + before do + visit project_commit_path(project, sample_commit.id, view: 'parallel') + end + + it_behaves_like 'copying code from a diff' + + context 'selecting code on the left' do + it 'copies as a code block' do + verify( + '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', + + <<-GFM.strip_heredoc, + ```ruby + unless cmd.is_a?(Array) + raise "System commands must be given as an array of strings" + end + ``` + GFM + + target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].left-side' + ) + end + end + + context 'selecting code on the right' do + it 'copies as a code block' do + verify( + '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', + + <<-GFM.strip_heredoc, + ```ruby + unless cmd.is_a?(Array) + raise RuntimeError, "System commands must be given as an array of strings" + end + ``` + GFM + + target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].right-side' + ) + end end end end @@ -587,9 +643,9 @@ describe 'Copy as GFM', js: true do end end - def verify(selector, gfm) + def verify(selector, gfm, target: nil) html = html_for_selector(selector) - output_gfm = html_to_gfm(html, 'transformCodeSelection') + output_gfm = html_to_gfm(html, 'transformCodeSelection', target: target) expect(output_gfm.strip).to eq(gfm.strip) end end @@ -605,15 +661,21 @@ describe 'Copy as GFM', js: true do page.evaluate_script(js) end - def html_to_gfm(html, transformer = 'transformGFMSelection') + def html_to_gfm(html, transformer = 'transformGFMSelection', target: nil) js = <<-JS.strip_heredoc (function(html) { var transformer = window.gl.CopyAsGFM[#{transformer.inspect}]; var node = document.createElement('div'); - node.innerHTML = html; + $(html).each(function() { node.appendChild(this) }); + + var targetSelector = #{target.to_json}; + var target; + if (targetSelector) { + target = document.querySelector(targetSelector); + } - node = transformer(node); + node = transformer(node, target); if (!node) return null; return window.gl.CopyAsGFM.nodeToGFM(node); diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index bfe9dac3bd4..177cd50dd72 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Cycle Analytics', js: true do +feature 'Cycle Analytics', :js do let(:user) { create(:user) } let(:guest) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb index 08d8cc7922b..8bab501134b 100644 --- a/spec/features/dashboard/active_tab_spec.rb +++ b/spec/features/dashboard/active_tab_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe 'Dashboard Active Tab', js: true do +RSpec.describe 'Dashboard Active Tab', :js do before do sign_in(create(:user)) end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index b6dce1b8ec4..349f9a47112 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Tooltips on .timeago dates', js: true do +feature 'Tooltips on .timeago dates', :js do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:created_date) { Date.yesterday.to_time } diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb index 60a16830cdc..1c7932e7964 100644 --- a/spec/features/dashboard/group_spec.rb +++ b/spec/features/dashboard/group_spec.rb @@ -5,9 +5,15 @@ RSpec.describe 'Dashboard Group' do sign_in(create(:user)) end - it 'creates new group', js: true do + it 'defaults sort dropdown to last created' do visit dashboard_groups_path - find('.btn-new').trigger('click') + + expect(page).to have_button('Last created') + end + + it 'creates new group', :js do + visit dashboard_groups_path + find('.btn-new').click new_path = 'Samurai' new_description = 'Tokugawa Shogunate' diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index 533df7a325c..d92c002b4e7 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -1,57 +1,81 @@ require 'spec_helper' feature 'Dashboard Groups page', :js do - let!(:user) { create :user } - let!(:group) { create(:group) } - let!(:nested_group) { create(:group, :nested) } - let!(:another_group) { create(:group) } + let(:user) { create :user } + let(:group) { create(:group) } + let(:nested_group) { create(:group, :nested) } + let(:another_group) { create(:group) } + + def click_group_caret(group) + within("#group-#{group.id}") do + first('.folder-caret').click + end + wait_for_requests + end it 'shows groups user is member of' do group.add_owner(user) nested_group.add_owner(user) + expect(another_group).to be_persisted + + sign_in(user) + visit dashboard_groups_path + wait_for_requests + + expect(page).to have_content(group.name) + + expect(page).not_to have_content(another_group.name) + end + + it 'shows subgroups the user is member of', :nested_groups do + group.add_owner(user) + nested_group.add_owner(user) sign_in(user) visit dashboard_groups_path + wait_for_requests - expect(page).to have_content(group.full_name) - expect(page).to have_content(nested_group.full_name) - expect(page).not_to have_content(another_group.full_name) + expect(page).to have_content(nested_group.parent.name) + click_group_caret(nested_group.parent) + expect(page).to have_content(nested_group.name) end - describe 'when filtering groups' do + describe 'when filtering groups', :nested_groups do before do group.add_owner(user) nested_group.add_owner(user) + expect(another_group).to be_persisted sign_in(user) visit dashboard_groups_path end - it 'filters groups' do - fill_in 'filter_groups', with: group.name + it 'expands when filtering groups' do + fill_in 'filter', with: nested_group.name wait_for_requests - expect(page).to have_content(group.full_name) - expect(page).not_to have_content(nested_group.full_name) - expect(page).not_to have_content(another_group.full_name) + expect(page).not_to have_content(group.name) + expect(page).to have_content(nested_group.parent.name) + expect(page).to have_content(nested_group.name) + expect(page).not_to have_content(another_group.name) end it 'resets search when user cleans the input' do - fill_in 'filter_groups', with: group.name + fill_in 'filter', with: group.name wait_for_requests - fill_in 'filter_groups', with: '' + fill_in 'filter', with: '' wait_for_requests - expect(page).to have_content(group.full_name) - expect(page).to have_content(nested_group.full_name) - expect(page).not_to have_content(another_group.full_name) + expect(page).to have_content(group.name) + expect(page).to have_content(nested_group.parent.name) + expect(page).not_to have_content(another_group.name) expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 end end - describe 'group with subgroups' do + describe 'group with subgroups', :nested_groups do let!(:subgroup) { create(:group, :public, parent: group) } before do @@ -64,33 +88,35 @@ feature 'Dashboard Groups page', :js do end it 'shows subgroups inside of its parent group' do - expect(page).to have_selector('.groups-list-tree-container .group-list-tree', count: 2) - expect(page).to have_selector(".groups-list-tree-container #group-#{group.id} #group-#{subgroup.id}", count: 1) + expect(page).to have_selector("#group-#{group.id}") + click_group_caret(group) + expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}") end it 'can toggle parent group' do - # Expanded by default - expect(page).to have_selector("#group-#{group.id} .fa-caret-down", count: 1) - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right") + # Collapsed by default + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1) + expect(page).to have_selector("#group-#{group.id} .fa-caret-right") - # Collapse - find("#group-#{group.id}").trigger('click') + # expand + click_group_caret(group) - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down") - expect(page).to have_selector("#group-#{group.id} .fa-caret-right", count: 1) - expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}") + expect(page).to have_selector("#group-#{group.id} .fa-caret-down") + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right", count: 1) + expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}") - # Expand - find("#group-#{group.id}").trigger('click') + # collapse + click_group_caret(group) - expect(page).to have_selector("#group-#{group.id} .fa-caret-down", count: 1) - expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right") - expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}") + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down", count: 1) + expect(page).to have_selector("#group-#{group.id} .fa-caret-right") + expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}") end end describe 'when using pagination' do - let(:group2) { create(:group) } + let(:group) { create(:group, created_at: 5.days.ago) } + let(:group2) { create(:group, created_at: 2.days.ago) } before do group.add_owner(user) @@ -102,12 +128,9 @@ feature 'Dashboard Groups page', :js do visit dashboard_groups_path end - it 'shows pagination' do - expect(page).to have_selector('.gl-pagination') + it 'loads results for next page' do expect(page).to have_selector('.gl-pagination .page', count: 2) - end - it 'loads results for next page' do # Check first page expect(page).to have_content(group2.full_name) expect(page).to have_selector("#group-#{group2.id}") @@ -115,7 +138,7 @@ feature 'Dashboard Groups page', :js do expect(page).not_to have_selector("#group-#{group.id}") # Go to next page - find(".gl-pagination .page:not(.active) a").trigger('click') + find(".gl-pagination .page:not(.active) a").click wait_for_requests diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 795335aa106..5b4c00b3c7e 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'Dashboard Issues' do expect(page).not_to have_content(other_issue.title) end - it 'shows checkmark when unassigned is selected for assignee', js: true do + it 'shows checkmark when unassigned is selected for assignee', :js do find('.js-assignee-search').click find('li', text: 'Unassigned').click find('.js-assignee-search').click @@ -32,8 +32,8 @@ RSpec.describe 'Dashboard Issues' do expect(find('li[data-user-id="0"] a.is-active')).to be_visible end - it 'shows issues when current user is author', js: true do - find('#assignee_id', visible: false).set('') + it 'shows issues when current user is author', :js do + execute_script("document.querySelector('#assignee_id').value=''") find('.js-author-search', match: :first).click expect(find('li[data-user-id="null"] a.is-active')).to be_visible @@ -70,8 +70,8 @@ RSpec.describe 'Dashboard Issues' do end describe 'new issue dropdown' do - it 'shows projects only with issues feature enabled', js: true do - find('.new-project-item-select-button').trigger('click') + it 'shows projects only with issues feature enabled', :js do + find('.new-project-item-select-button').click page.within('.select2-results') do expect(page).to have_content(project.name_with_namespace) @@ -79,19 +79,21 @@ RSpec.describe 'Dashboard Issues' do end end - it 'shows the new issue page', js: true do - find('.new-project-item-select-button').trigger('click') + it 'shows the new issue page', :js do + find('.new-project-item-select-button').click wait_for_requests project_path = "/#{project.path_with_namespace}" project_json = { name: project.name_with_namespace, url: project_path }.to_json - # similate selection, and prevent overlap by dropdown menu + # simulate selection, and prevent overlap by dropdown menu + first('.project-item-select', visible: false) execute_script("$('.project-item-select').val('#{project_json}').trigger('change');") + find('#select2-drop-mask', visible: false) execute_script("$('#select2-drop-mask').remove();") - find('.new-project-item-link').trigger('click') + find('.new-project-item-link').click expect(page).to have_current_path("#{project_path}/issues/new") diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb index b1a207682c3..6802974c2ee 100644 --- a/spec/features/dashboard/label_filter_spec.rb +++ b/spec/features/dashboard/label_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Dashboard > label filter', js: true do +describe 'Dashboard > label filter', :js do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) } diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 8204828b5b9..991d360ccaf 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' feature 'Dashboard Merge Requests' do include FilterItemSelectHelper include SortingHelper + include ProjectForksHelper let(:current_user) { create :user } let(:project) { create(:project) } let(:public_project) { create(:project, :public, :repository) } - let(:forked_project) { Projects::ForkService.new(public_project, current_user).execute } + let(:forked_project) { fork_project(public_project, current_user, repository: true) } before do project.add_master(current_user) @@ -23,8 +24,8 @@ feature 'Dashboard Merge Requests' do visit merge_requests_dashboard_path end - it 'shows projects only with merge requests feature enabled', js: true do - find('.new-project-item-select-button').trigger('click') + it 'shows projects only with merge requests feature enabled', :js do + find('.new-project-item-select-button').click page.within('.select2-results') do expect(page).to have_content(project.name_with_namespace) @@ -88,7 +89,7 @@ feature 'Dashboard Merge Requests' do expect(page).not_to have_content(other_merge_request.title) end - it 'shows authored merge requests', js: true do + it 'shows authored merge requests', :js do filter_item_select('Any Assignee', '.js-assignee-search') filter_item_select(current_user.to_reference, '.js-author-search') @@ -100,7 +101,7 @@ feature 'Dashboard Merge Requests' do expect(page).not_to have_content(other_merge_request.title) end - it 'shows all merge requests', js: true do + it 'shows all merge requests', :js do filter_item_select('Any Assignee', '.js-assignee-search') filter_item_select('Any Author', '.js-author-search') diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index 4a004107408..8f96899fb4f 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Project member activity', js: true do +feature 'Project member activity', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, name: 'x', namespace: user.namespace) } diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 4da95ccc169..fbf8b5c0db6 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -80,7 +80,7 @@ feature 'Dashboard Projects' do end end - describe 'with a pipeline', clean_gitlab_redis_shared_state: true do + describe 'with a pipeline', :clean_gitlab_redis_shared_state do let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) } before do diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index 54d477f7274..ad0f132da8c 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Dashboard > User filters todos', js: true do +feature 'Dashboard > User filters todos', :js do let(:user_1) { create(:user, username: 'user_1', name: 'user_1') } let(:user_2) { create(:user, username: 'user_2', name: 'user_2') } diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 30bab7eeaa7..6f916078b1a 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -17,7 +17,7 @@ feature 'Dashboard Todos' do end end - context 'User has a todo', js: true do + context 'User has a todo', :js do before do create(:todo, :mentioned, user: user, project: project, target: issue, author: author) sign_in(user) @@ -52,7 +52,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 0' + expect(page).to have_content 'Todos 0' expect(page).to have_content 'Done 1' end @@ -81,7 +81,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 1' + expect(page).to have_content 'Todos 1' expect(page).to have_content 'Done 0' end end @@ -177,7 +177,7 @@ feature 'Dashboard Todos' do end end - context 'User has done todos', js: true do + context 'User has done todos', :js do before do create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author) sign_in(user) @@ -200,7 +200,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 1' + expect(page).to have_content 'Todos 1' expect(page).to have_content 'Done 0' end end @@ -249,14 +249,14 @@ feature 'Dashboard Todos' do expect(page).to have_selector('.gl-pagination .page', count: 2) end - describe 'mark all as done', js: true do + describe 'mark all as done', :js do before do visit dashboard_todos_path - find('.js-todos-mark-all').trigger('click') + find('.js-todos-mark-all').click end it 'shows "All done" message!' do - expect(page).to have_content 'To do 0' + expect(page).to have_content 'Todos 0' expect(page).to have_content "You're all done!" expect(page).not_to have_selector('.gl-pagination') end @@ -267,7 +267,7 @@ feature 'Dashboard Todos' do end end - describe 'undo mark all as done', js: true do + describe 'undo mark all as done', :js do before do visit dashboard_todos_path end @@ -283,7 +283,7 @@ feature 'Dashboard Todos' do it 'updates todo count' do mark_all_and_undo - expect(page).to have_content 'To do 2' + expect(page).to have_content 'Todos 2' expect(page).to have_content 'Done 0' end @@ -309,9 +309,9 @@ feature 'Dashboard Todos' do end def mark_all_and_undo - find('.js-todos-mark-all').trigger('click') + find('.js-todos-mark-all').click wait_for_requests - find('.js-todos-undo-all').trigger('click') + find('.js-todos-undo-all').click wait_for_requests end end diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb index 0375d0bf8ff..69d35cdbc72 100644 --- a/spec/features/discussion_comments/commit_spec.rb +++ b/spec/features/discussion_comments/commit_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Discussion Comments Merge Request', :js do +describe 'Discussion Comments Commit', :js do include RepoHelpers let(:user) { create(:user) } diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb index 1e6389d9a13..4a236c4639b 100644 --- a/spec/features/discussion_comments/snippets_spec.rb +++ b/spec/features/discussion_comments/snippets_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Discussion Comments Issue', :js do +describe 'Discussion Comments Snippet', :js do let(:user) { create(:user) } let(:project) { create(:project) } let(:snippet) { create(:project_snippet, :private, project: project, author: user) } diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 357d86497d9..1dd7547a7fc 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Expand and collapse diffs', js: true do +feature 'Expand and collapse diffs', :js do let(:branch) { 'expand-collapse-diffs' } let(:project) { create(:project, :repository) } diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index b5325301968..801a33979ff 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -13,6 +13,7 @@ describe 'Explore Groups page', :js do sign_in(user) visit explore_groups_path + wait_for_requests end it 'shows groups user is member of' do @@ -22,7 +23,7 @@ describe 'Explore Groups page', :js do end it 'filters groups' do - fill_in 'filter_groups', with: group.name + fill_in 'filter', with: group.name wait_for_requests expect(page).to have_content(group.full_name) @@ -31,10 +32,10 @@ describe 'Explore Groups page', :js do end it 'resets search when user cleans the input' do - fill_in 'filter_groups', with: group.name + fill_in 'filter', with: group.name wait_for_requests - fill_in 'filter_groups', with: "" + fill_in 'filter', with: "" wait_for_requests expect(page).to have_content(group.full_name) @@ -45,21 +46,21 @@ describe 'Explore Groups page', :js do it 'shows non-archived projects count' do # Initially project is not archived - expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + expect(find('.js-groups-list-holder .content-list li:first-child .stats .number-projects')).to have_text("1") # Archive project empty_project.archive! visit explore_groups_path # Check project count - expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0") + expect(find('.js-groups-list-holder .content-list li:first-child .stats .number-projects')).to have_text("0") # Unarchive project empty_project.unarchive! visit explore_groups_path # Check project count - expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1") + expect(find('.js-groups-list-holder .content-list li:first-child .stats .number-projects')).to have_text("1") end describe 'landing component' do diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index e1c74a24890..8d5233d0c0f 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -65,9 +65,9 @@ feature 'Top Plus Menu', :js do visit project_path(project) page.within '.header-content' do - find('.header-new-dropdown-toggle').trigger('click') + find('.header-new-dropdown-toggle').click expect(page).to have_selector('.header-new.dropdown.open', count: 1) - find('.header-new-project-snippet a').trigger('click') + find('.header-new-project-snippet a').click end expect(page).to have_content('New Snippet') @@ -87,9 +87,9 @@ feature 'Top Plus Menu', :js do visit group_path(group) page.within '.header-content' do - find('.header-new-dropdown-toggle').trigger('click') + find('.header-new-dropdown-toggle').click expect(page).to have_selector('.header-new.dropdown.open', count: 1) - find('.header-new-group-project a').trigger('click') + find('.header-new-group-project a').click end expect(page).to have_content('Project path') @@ -128,12 +128,6 @@ feature 'Top Plus Menu', :js do expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet') end - scenario 'public project has no New Issue Button' do - visit project_path(public_project) - - hasnot_topmenuitem("New issue") - end - scenario 'public project has no New merge request menu item' do visit project_path(public_project) @@ -161,7 +155,7 @@ feature 'Top Plus Menu', :js do def click_topmenuitem(item_name) page.within '.header-content' do - find('.header-new-dropdown-toggle').trigger('click') + find('.header-new-dropdown-toggle').click expect(page).to have_selector('.header-new.dropdown.open', count: 1) click_link item_name end diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb new file mode 100644 index 00000000000..6ac9497b024 --- /dev/null +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -0,0 +1,72 @@ +require 'spec_helper' + +describe 'User explores projects' do + set(:archived_project) { create(:project, :archived) } + set(:internal_project) { create(:project, :internal) } + set(:private_project) { create(:project, :private) } + set(:public_project) { create(:project, :public) } + + shared_examples_for 'shows public projects' do + it 'shows projects' do + expect(page).to have_content(public_project.title) + expect(page).not_to have_content(internal_project.title) + expect(page).not_to have_content(private_project.title) + expect(page).not_to have_content(archived_project.title) + end + end + + shared_examples_for 'shows public and internal projects' do + it 'shows projects' do + expect(page).to have_content(public_project.title) + expect(page).to have_content(internal_project.title) + expect(page).not_to have_content(private_project.title) + expect(page).not_to have_content(archived_project.title) + end + end + + context 'when not signed in' do + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public projects' + end + end + + context 'when signed in' do + set(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when viewing public projects' do + before do + visit(explore_projects_path) + end + + include_examples 'shows public and internal projects' + end + + context 'when viewing most starred projects' do + before do + visit(starred_explore_projects_path) + end + + include_examples 'shows public and internal projects' + end + + context 'when viewing trending projects' do + before do + [archived_project, public_project].each { |project| create(:note_on_issue, project: project) } + + TrendingProject.refresh! + + visit(trending_explore_projects_path) + end + + include_examples 'shows public projects' + end + end +end diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 53b3bb3b65f..3c2186b3598 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -49,7 +49,7 @@ describe "GitLab Flavored Markdown" do end end - describe "for issues", js: true do + describe "for issues", :js do before do @other_issue = create(:issue, author: user, diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 37814ba6238..d2d0be35f1c 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Group variables', js: true do +feature 'Group variables', :js do let(:user) { create(:user) } let(:group) { create(:group) } diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb index 1dd09d4f203..2e06caf98f6 100644 --- a/spec/features/groups/labels/subscription_spec.rb +++ b/spec/features/groups/labels/subscription_spec.rb @@ -11,7 +11,7 @@ feature 'Labels subscription' do gitlab_sign_in user end - scenario 'users can subscribe/unsubscribe to group labels', js: true do + scenario 'users can subscribe/unsubscribe to group labels', :js do visit group_labels_path(group) expect(page).to have_content('feature') diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 56144d17d4f..1b41b3842c8 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -18,6 +18,27 @@ feature 'Group milestones', :js do visit new_group_milestone_path(group) end + it 'renders description preview' do + description = find('.note-textarea') + + description.native.send_keys('') + + click_link('Preview') + + preview = find('.js-md-preview') + + expect(preview).to have_content('Nothing to preview.') + + click_link('Write') + + description.native.send_keys(':+1: Nice') + + click_link('Preview') + + expect(preview).to have_css('gl-emoji') + expect(find('#milestone_description', visible: false)).not_to be_visible + end + it 'creates milestone with start date' do fill_in 'Title', with: 'testing' find('#milestone_start_date').click @@ -30,6 +51,13 @@ feature 'Group milestones', :js do expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y')) end + + it 'description input does not support autocomplete' do + description = find('.note-textarea') + description.native.send_keys('!') + + expect(page).not_to have_selector('.atwho-view') + end end context 'milestones list' do diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 303013e59d5..7fc2b383749 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -24,4 +24,35 @@ feature 'Group show page' do it_behaves_like "an autodiscoverable RSS feed without an RSS token" end + + context 'subgroup support' do + let(:user) { create(:user) } + + before do + group.add_owner(user) + sign_in(user) + end + + context 'when subgroups are supported', :js, :nested_groups do + before do + allow(Group).to receive(:supports_nested_groups?) { true } + visit path + end + + it 'allows creating subgroups' do + expect(page).to have_css("li[data-text='New subgroup']", visible: false) + end + end + + context 'when subgroups are not supported' do + before do + allow(Group).to receive(:supports_nested_groups?) { false } + visit path + end + + it 'allows creating subgroups' do + expect(page).not_to have_selector("li[data-text='New subgroup']", visible: false) + end + end + end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 4ec2e7e6012..c1f3d94bc20 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -65,7 +65,7 @@ feature 'Group' do end it 'updates the team URL on graph path update', :js do - out_span = find('span[data-bind-out="create_chat_team"]') + out_span = find('span[data-bind-out="create_chat_team"]', visible: false) expect(out_span.text).to be_empty @@ -85,13 +85,12 @@ feature 'Group' do end end - describe 'create a nested group', :nested_groups, js: true do + describe 'create a nested group', :nested_groups, :js do let(:group) { create(:group, path: 'foo') } context 'as admin' do before do - visit subgroups_group_path(group) - click_link 'New Subgroup' + visit new_group_path(group, parent_id: group.id) end it 'creates a nested group' do @@ -111,8 +110,8 @@ feature 'Group' do sign_out(:user) sign_in(user) - visit subgroups_group_path(group) - click_link 'New Subgroup' + visit new_group_path(group, parent_id: group.id) + fill_in 'Group path', with: 'bar' click_button 'Create group' @@ -120,16 +119,6 @@ feature 'Group' do expect(page).to have_content("Group 'bar' was successfully created.") end end - - context 'when nested group feature is disabled' do - it 'renders 404' do - allow(Group).to receive(:supports_nested_groups?).and_return(false) - - visit subgroups_group_path(group) - - expect(page.status_code).to eq(404) - end - end end it 'checks permissions to avoid exposing groups by parent_id' do @@ -142,7 +131,7 @@ feature 'Group' do expect(page).not_to have_content('secret-group') end - describe 'group edit', js: true do + describe 'group edit', :js do let(:group) { create(:group) } let(:path) { edit_group_path(group) } let(:new_name) { 'new-name' } @@ -207,16 +196,18 @@ feature 'Group' do end end - describe 'group page with nested groups', :nested_groups, js: true do + describe 'group page with nested groups', :nested_groups, :js do let!(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } + let!(:project) { create(:project, namespace: group) } let!(:path) { group_path(group) } - it 'has nested groups tab with nested groups inside' do + it 'it renders projects and groups on the page' do visit path - click_link 'Subgroups' + wait_for_requests expect(page).to have_content(nested_group.name) + expect(page).to have_content(project.name) end end diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb index 925d026ed61..caee7a67aec 100644 --- a/spec/features/issuables/default_sort_order_spec.rb +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -26,7 +26,7 @@ describe 'Projects > Issuables > Default sort order' do MergeRequest.all end - context 'in the "merge requests" tab', js: true do + context 'in the "merge requests" tab', :js do let(:issuable_type) { :merge_request } it 'is "last created"' do @@ -37,7 +37,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "merge requests / open" tab', js: true do + context 'in the "merge requests / open" tab', :js do let(:issuable_type) { :merge_request } it 'is "created date"' do @@ -49,7 +49,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "merge requests / merged" tab', js: true do + context 'in the "merge requests / merged" tab', :js do let(:issuable_type) { :merged_merge_request } it 'is "last updated"' do @@ -61,7 +61,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "merge requests / closed" tab', js: true do + context 'in the "merge requests / closed" tab', :js do let(:issuable_type) { :closed_merge_request } it 'is "last updated"' do @@ -73,7 +73,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "merge requests / all" tab', js: true do + context 'in the "merge requests / all" tab', :js do let(:issuable_type) { :merge_request } it 'is "created date"' do @@ -102,7 +102,7 @@ describe 'Projects > Issuables > Default sort order' do Issue.all end - context 'in the "issues" tab', js: true do + context 'in the "issues" tab', :js do let(:issuable_type) { :issue } it 'is "created date"' do @@ -114,7 +114,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "issues / open" tab', js: true do + context 'in the "issues / open" tab', :js do let(:issuable_type) { :issue } it 'is "created date"' do @@ -126,7 +126,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "issues / closed" tab', js: true do + context 'in the "issues / closed" tab', :js do let(:issuable_type) { :closed_issue } it 'is "last updated"' do @@ -138,7 +138,7 @@ describe 'Projects > Issuables > Default sort order' do end end - context 'in the "issues / all" tab', js: true do + context 'in the "issues / all" tab', :js do let(:issuable_type) { :issue } it 'is "created date"' do diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issuables/discussion_lock_spec.rb new file mode 100644 index 00000000000..7ea29ff252b --- /dev/null +++ b/spec/features/issuables/discussion_lock_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' + +describe 'Discussion Lock', :js do + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, author: user) } + let(:project) { create(:project, :public) } + + before do + sign_in(user) + end + + context 'when a user is a team member' do + before do + project.add_developer(user) + end + + context 'when the discussion is unlocked' do + it 'the user can lock the issue' do + visit project_issue_path(project, issue) + + expect(find('.issuable-sidebar')).to have_content('Unlocked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Lock') + end + + expect(find('#notes')).to have_content('locked this issue') + end + end + + context 'when the discussion is locked' do + before do + issue.update_attribute(:discussion_locked, true) + visit project_issue_path(project, issue) + end + + it 'the user can unlock the issue' do + expect(find('.issuable-sidebar')).to have_content('Locked') + + page.within('.issuable-sidebar') do + find('.lock-edit').click + click_button('Unlock') + end + + expect(find('#notes')).to have_content('unlocked this issue') + expect(find('.issuable-sidebar')).to have_content('Unlocked') + end + + it 'the user can create a comment' do + page.within('#notes .js-main-target-form') do + fill_in 'note[note]', with: 'Some new comment' + click_button 'Comment' + end + + wait_for_requests + + expect(find('div#notes')).to have_content('Some new comment') + end + end + end + + context 'when a user is not a team member' do + context 'when the discussion is unlocked' do + before do + visit project_issue_path(project, issue) + end + + it 'the user can not lock the issue' do + expect(find('.issuable-sidebar')).to have_content('Unlocked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + + it 'the user can create a comment' do + page.within('#notes .js-main-target-form') do + fill_in 'note[note]', with: 'Some new comment' + click_button 'Comment' + end + + wait_for_requests + + expect(find('div#notes')).to have_content('Some new comment') + end + end + + context 'when the discussion is locked' do + before do + issue.update_attribute(:discussion_locked, true) + visit project_issue_path(project, issue) + end + + it 'the user can not unlock the issue' do + expect(find('.issuable-sidebar')).to have_content('Locked') + expect(find('.issuable-sidebar')).not_to have_selector('.lock-edit') + end + + it 'the user can not create a comment' do + page.within('#notes') do + expect(page).not_to have_selector('js-main-target-form') + expect(page.find('.disabled-comment')) + .to have_content('This issue is locked. Only project members can comment.') + end + end + end + end +end diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb index 2bd1c8aab86..c6c2e58ecea 100644 --- a/spec/features/issuables/user_sees_sidebar_spec.rb +++ b/spec/features/issuables/user_sees_sidebar_spec.rb @@ -12,7 +12,7 @@ describe 'Issue Sidebar on Mobile' do sign_in(user) end - context 'mobile sidebar on merge requests', js: true do + context 'mobile sidebar on merge requests', :js do before do visit project_merge_request_path(merge_request.project, merge_request) end @@ -20,7 +20,7 @@ describe 'Issue Sidebar on Mobile' do it_behaves_like "issue sidebar stays collapsed on mobile" end - context 'mobile sidebar on issues', js: true do + context 'mobile sidebar on issues', :js do before do visit project_issue_path(project, issue) end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index a29acb30163..850b35c4467 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -24,7 +24,7 @@ describe 'Awards Emoji' do end # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 - it 'does not shows a 500 page', js: true do + it 'does not shows a 500 page', :js do expect(page).to have_text(issue.title) end end @@ -37,37 +37,37 @@ describe 'Awards Emoji' do wait_for_requests end - it 'increments the thumbsdown emoji', js: true do + it 'increments the thumbsdown emoji', :js do find('[data-name="thumbsdown"]').click wait_for_requests expect(thumbsdown_emoji).to have_text("1") end context 'click the thumbsup emoji' do - it 'increments the thumbsup emoji', js: true do + it 'increments the thumbsup emoji', :js do find('[data-name="thumbsup"]').click wait_for_requests expect(thumbsup_emoji).to have_text("1") end - it 'decrements the thumbsdown emoji', js: true do + it 'decrements the thumbsdown emoji', :js do expect(thumbsdown_emoji).to have_text("0") end end context 'click the thumbsdown emoji' do - it 'increments the thumbsdown emoji', js: true do + it 'increments the thumbsdown emoji', :js do find('[data-name="thumbsdown"]').click wait_for_requests expect(thumbsdown_emoji).to have_text("1") end - it 'decrements the thumbsup emoji', js: true do + it 'decrements the thumbsup emoji', :js do expect(thumbsup_emoji).to have_text("0") end end - it 'toggles the smiley emoji on a note', js: true do + it 'toggles the smiley emoji on a note', :js do toggle_smiley_emoji(true) within('.note-body') do @@ -82,7 +82,7 @@ describe 'Awards Emoji' do end context 'execute /award quick action' do - it 'toggles the emoji award on noteable', js: true do + it 'toggles the emoji award on noteable', :js do execute_quick_action('/award :100:') expect(find(noteable_award_counter)).to have_text("1") @@ -95,7 +95,7 @@ describe 'Awards Emoji' do end end - context 'unauthorized user', js: true do + context 'unauthorized user', :js do before do visit project_issue_path(project, issue) end diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb index e95eb19f7d1..ddb69d414da 100644 --- a/spec/features/issues/award_spec.rb +++ b/spec/features/issues/award_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Issue awards', js: true do +feature 'Issue awards', :js do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb index b2229b44f99..fa4d3a55c62 100644 --- a/spec/features/issues/bulk_assignment_labels_spec.rb +++ b/spec/features/issues/bulk_assignment_labels_spec.rb @@ -9,7 +9,7 @@ feature 'Issues > Labels bulk assignment' do let!(:feature) { create(:label, project: project, title: 'feature') } let!(:wontfix) { create(:label, project: project, title: 'wontfix') } - context 'as an allowed user', js: true do + context 'as an allowed user', :js do before do project.team << [user, :master] @@ -405,7 +405,7 @@ feature 'Issues > Labels bulk assignment' do end def update_issues - click_button 'Update all' + find('.update-selected-issues').click wait_for_requests end diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb index 546dc7e8a49..edea95c6699 100644 --- a/spec/features/issues/create_branch_merge_request_spec.rb +++ b/spec/features/issues/create_branch_merge_request_spec.rb @@ -64,6 +64,19 @@ feature 'Create Branch/Merge Request Dropdown on issue page', :feature, :js do end end + context 'when merge requests are disabled' do + before do + project.project_feature.update(merge_requests_access_level: 0) + + visit project_issue_path(project, issue) + end + + it 'shows only create branch button' do + expect(page).not_to have_button('Create a merge request') + expect(page).to have_button('Create a branch') + end + end + context 'when issue is confidential' do it 'disables the create branch button' do issue = create(:issue, :confidential, project: project) diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb index 80cc8d22999..822ba48e005 100644 --- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Resolving all open discussions in a merge request from an issue', js: true do +feature 'Resolving all open discussions in a merge request from an issue', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index ad5fd0fd97b..f0bed85595c 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -24,7 +24,7 @@ feature 'Resolve an open discussion in a merge request by creating an issue' do end end - context 'resolving the discussion', js: true do + context 'resolving the discussion', :js do before do click_button 'Resolve discussion' end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 1c4649d0ba9..2e4a25ee15d 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -43,15 +43,16 @@ describe 'Dropdown assignee', :js do end it 'should show loading indicator when opened' do - filtered_search.set('assignee:') + slow_requests do + filtered_search.set('assignee:') - expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true) + expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true) + end end it 'should hide loading indicator when loaded' do filtered_search.set('assignee:') - expect(find(js_dropdown_assignee)).to have_css('.filter-dropdown-loading') expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading') end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index 3cec59050ab..2fb5e7cdba4 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Dropdown author', js: true do +describe 'Dropdown author', :js do include FilteredSearchHelpers let!(:project) { create(:project) } @@ -51,9 +51,11 @@ describe 'Dropdown author', js: true do end it 'should show loading indicator when opened' do - filtered_search.set('author:') + slow_requests do + filtered_search.set('author:') - expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true) + expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true) + end end it 'should hide loading indicator when loaded' do diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index 44741bcc92d..8db435634fd 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Dropdown emoji', js: true do +describe 'Dropdown emoji', :js do include FilteredSearchHelpers let!(:project) { create(:project, :public) } @@ -70,9 +70,11 @@ describe 'Dropdown emoji', js: true do end it 'should show loading indicator when opened' do - filtered_search.set('my-reaction:') + slow_requests do + filtered_search.set('my-reaction:') - expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true) + expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true) + end end it 'should hide loading indicator when loaded' do diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index c46803112a9..18cdb199c70 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Dropdown label', js: true do +describe 'Dropdown label', :js do include FilteredSearchHelpers let(:project) { create(:project) } @@ -66,9 +66,11 @@ describe 'Dropdown label', js: true do end it 'shows loading indicator when opened and hides it when loaded' do - filtered_search.set('label:') + slow_requests do + filtered_search.set('label:') - expect(find(js_dropdown_label)).to have_css('.filter-dropdown-loading') + expect(page).to have_css("#{js_dropdown_label} .filter-dropdown-loading", visible: true) + end expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading') end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index f6c2e952bea..031eb06723a 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -50,15 +50,16 @@ describe 'Dropdown milestone', :js do end it 'should show loading indicator when opened' do - filtered_search.set('milestone:') + slow_requests do + filtered_search.set('milestone:') - expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true) + expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true) + end end it 'should hide loading indicator when loaded' do filtered_search.set('milestone:') - expect(find(js_dropdown_milestone)).to have_css('.filter-dropdown-loading') expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading') end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 630d6a10c9c..b3c50964810 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Filter issues', js: true do +describe 'Filter issues', :js do include FilteredSearchHelpers let(:project) { create(:project) } @@ -139,7 +139,7 @@ describe 'Filter issues', js: true do input_filtered_search('label:none') expect_tokens([label_token('none', false)]) - expect_issues_list_count(8) + expect_issues_list_count(4) expect_filtered_search_input_empty end diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index 5eeecaeda47..f355cec3ba9 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Recent searches', js: true do +describe 'Recent searches', :js do include FilteredSearchHelpers let(:project_1) { create(:project, :public) } @@ -27,9 +27,8 @@ describe 'Recent searches', js: true do input_filtered_search('foo', submit: true) input_filtered_search('bar', submit: true) - items = all('.filtered-search-history-dropdown-item', visible: false) + items = all('.filtered-search-history-dropdown-item', visible: false, count: 2) - expect(items.count).to eq(2) expect(items[0].text).to eq('bar') expect(items[1].text).to eq('foo') end @@ -38,9 +37,8 @@ describe 'Recent searches', js: true do visit project_issues_path(project_1, label_name: 'foo', search: 'bar') visit project_issues_path(project_1, label_name: 'qux', search: 'garply') - items = all('.filtered-search-history-dropdown-item', visible: false) + items = all('.filtered-search-history-dropdown-item', visible: false, count: 2) - expect(items.count).to eq(2) expect(items[0].text).to eq('label:~qux garply') expect(items[1].text).to eq('label:~foo bar') end @@ -50,9 +48,8 @@ describe 'Recent searches', js: true do visit project_issues_path(project_1, search: 'foo') - items = all('.filtered-search-history-dropdown-item', visible: false) + items = all('.filtered-search-history-dropdown-item', visible: false, count: 3) - expect(items.count).to eq(3) expect(items[0].text).to eq('foo') expect(items[1].text).to eq('saved1') expect(items[2].text).to eq('saved2') @@ -69,9 +66,8 @@ describe 'Recent searches', js: true do input_filtered_search('more', submit: true) input_filtered_search('things', submit: true) - items = all('.filtered-search-history-dropdown-item', visible: false) + items = all('.filtered-search-history-dropdown-item', visible: false, count: 2) - expect(items.count).to eq(2) expect(items[0].text).to eq('things') expect(items[1].text).to eq('more') end @@ -80,7 +76,8 @@ describe 'Recent searches', js: true do set_recent_searches(project_1_local_storage_key, '["foo", "bar"]') visit project_issues_path(project_1) - all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click') + find('.filtered-search-history-dropdown-toggle-button').click + all('.filtered-search-history-dropdown-item', count: 2)[0].click wait_for_filtered_search('foo') expect(find('.filtered-search').value.strip).to eq('foo') @@ -90,12 +87,11 @@ describe 'Recent searches', js: true do set_recent_searches(project_1_local_storage_key, '["foo"]') visit project_issues_path(project_1) - items_before = all('.filtered-search-history-dropdown-item', visible: false) + find('.filtered-search-history-dropdown-toggle-button').click + all('.filtered-search-history-dropdown-item', count: 1) - expect(items_before.count).to eq(1) - - find('.filtered-search-history-clear-button', visible: false).trigger('click') - items_after = all('.filtered-search-history-dropdown-item', visible: false) + find('.filtered-search-history-clear-button').click + items_after = all('.filtered-search-history-dropdown-item', count: 0) expect(items_after.count).to eq(0) end @@ -104,6 +100,6 @@ describe 'Recent searches', js: true do set_recent_searches(project_1_local_storage_key, 'fail') visit project_issues_path(project_1) - expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches') + expect(find('.flash-alert')).to have_text('An error occurred while parsing recent searches') end end diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index d4dd570fb37..88688422dc7 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Search bar', js: true do +describe 'Search bar', :js do include FilteredSearchHelpers let!(:project) { create(:project) } diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index 2b624f4842d..0ae70c855db 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -1,8 +1,7 @@ require 'rails_helper' -describe 'Visual tokens', js: true do +describe 'Visual tokens', :js do include FilteredSearchHelpers - include WaitForRequests let!(:project) { create(:project) } let!(:user) { create(:user, name: 'administrator', username: 'root') } @@ -28,7 +27,7 @@ describe 'Visual tokens', js: true do sign_in(user) create(:issue, project: project) - page.driver.set_cookie('sidebar_collapsed', 'true') + set_cookie('sidebar_collapsed', 'true') visit project_issues_path(project) end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c6cf6265645..b8a66245153 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'GFM autocomplete', js: true do +feature 'GFM autocomplete', :js do let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } @@ -17,9 +17,9 @@ feature 'GFM autocomplete', js: true do it 'updates issue descripton with GFM reference' do find('.issuable-edit').click - find('#issue-description').native.send_keys("@#{user.name[0...3]}") + simulate_input('#issue-description', "@#{user.name[0...3]}") - find('.atwho-view .cur').trigger('click') + find('.atwho-view .cur').click click_button 'Save changes' @@ -28,7 +28,6 @@ feature 'GFM autocomplete', js: true do it 'opens autocomplete menu when field starts with text' do page.within '.timeline-content-form' do - find('#note-body').native.send_keys('') find('#note-body').native.send_keys('@') end @@ -46,7 +45,6 @@ feature 'GFM autocomplete', js: true do it 'doesnt select the first item for non-assignee dropdowns' do page.within '.timeline-content-form' do - find('#note-body').native.send_keys('') find('#note-body').native.send_keys(':') end @@ -86,7 +84,6 @@ feature 'GFM autocomplete', js: true do it 'selects the first item for assignee dropdowns' do page.within '.timeline-content-form' do - find('#note-body').native.send_keys('') find('#note-body').native.send_keys('@') end @@ -100,7 +97,7 @@ feature 'GFM autocomplete', js: true do it 'includes items for assignee dropdowns with non-ASCII characters in name' do page.within '.timeline-content-form' do find('#note-body').native.send_keys('') - find('#note-body').native.send_keys("@#{user.name[0...8]}") + simulate_input('#note-body', "@#{user.name[0...8]}") end expect(page).to have_selector('.atwho-container') @@ -112,7 +109,6 @@ feature 'GFM autocomplete', js: true do it 'selects the first item for non-assignee dropdowns if a query is entered' do page.within '.timeline-content-form' do - find('#note-body').native.send_keys('') find('#note-body').native.send_keys(':1') end @@ -127,9 +123,8 @@ feature 'GFM autocomplete', js: true do it 'wraps the result in double quotes' do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') - note.native.send_keys("~#{label.title[0]}") - note.click + find('#note-body').native.send_keys('') + simulate_input('#note-body', "~#{label.title[0]}") end label_item = find('.atwho-view li', text: label.title) @@ -152,16 +147,13 @@ feature 'GFM autocomplete', js: true do it "does not show dropdown when preceded with a special character" do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') note.native.send_keys("@") - note.click end expect(page).to have_selector('.atwho-container') page.within '.timeline-content-form' do note.native.send_keys("@") - note.click end expect(page).to have_selector('.atwho-container', visible: false) @@ -170,9 +162,7 @@ feature 'GFM autocomplete', js: true do it "does not throw an error if no labels exist" do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') note.native.send_keys('~') - note.click end expect(page).to have_selector('.atwho-container', visible: false) @@ -181,9 +171,7 @@ feature 'GFM autocomplete', js: true do it 'doesn\'t wrap for assignee values' do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') note.native.send_keys("@#{user.username[0]}") - note.click end user_item = find('.atwho-view li', text: user.username) @@ -194,9 +182,7 @@ feature 'GFM autocomplete', js: true do it 'doesn\'t wrap for emoji values' do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') - note.native.send_keys(":cartwheel") - note.click + note.native.send_keys(":cartwheel_") end emoji_item = find('.atwho-view li', text: 'cartwheel_tone1') @@ -223,12 +209,11 @@ feature 'GFM autocomplete', js: true do it 'triggers autocomplete after selecting a quick action' do note = find('#note-body') page.within '.timeline-content-form' do - note.native.send_keys('') note.native.send_keys('/as') - note.click end - find('.atwho-view li', text: '/assign').native.send_keys(:tab) + find('.atwho-view li', text: '/assign') + note.native.send_keys(:tab) user_item = find('.atwho-view li', text: user.username) expect(user_item).to have_content(user.username) diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 28b636f9359..6fbee0ebcb5 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -25,11 +25,10 @@ feature 'Issue Detail', :js do wait_for_requests click_link 'Edit' - fill_in 'issue-title', with: 'issue title' + fill_in 'issuable-title', with: 'issue title' click_button 'Save' - visit profile_account_path - click_link 'Delete account' + Users::DestroyService.new(user).execute(user) visit project_issue_path(project, issue) end diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index af11b474842..a9de52bd8d5 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -13,7 +13,7 @@ feature 'Issue Sidebar' do sign_in(user) end - context 'assignee', js: true do + context 'assignee', :js do let(:user2) { create(:user) } let(:issue2) { create(:issue, project: project, author: user2) } @@ -82,7 +82,7 @@ feature 'Issue Sidebar' do visit_issue(project, issue) end - context 'sidebar', js: true do + context 'sidebar', :js do it 'changes size when the screen size is smaller' do sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed' # Resize the window @@ -101,7 +101,7 @@ feature 'Issue Sidebar' do end end - context 'editing issue labels', js: true do + context 'editing issue labels', :js do before do page.within('.block.labels') do find('.edit-link').click @@ -114,7 +114,7 @@ feature 'Issue Sidebar' do end end - context 'creating a new label', js: true do + context 'creating a new label', :js do before do page.within('.block.labels') do click_link 'Create new' @@ -130,8 +130,8 @@ feature 'Issue Sidebar' do it 'adds new label' do page.within('.block.labels') do fill_in 'new_label_name', with: 'wontfix' - page.find('.suggest-colors a', match: :first).trigger('click') - page.find('button', text: 'Create').trigger('click') + page.find('.suggest-colors a', match: :first).click + page.find('button', text: 'Create').click page.within('.dropdown-page-one') do expect(page).to have_content 'wontfix' @@ -142,8 +142,8 @@ feature 'Issue Sidebar' do it 'shows error message if label title is taken' do page.within('.block.labels') do fill_in 'new_label_name', with: label.title - page.find('.suggest-colors a', match: :first).trigger('click') - page.find('button', text: 'Create').trigger('click') + page.find('.suggest-colors a', match: :first).click + page.find('button', text: 'Create').click page.within('.dropdown-page-two') do expect(page).to have_content 'Title has already been taken' @@ -170,7 +170,7 @@ feature 'Issue Sidebar' do end def open_issue_sidebar - find('aside.right-sidebar.right-sidebar-collapsed .js-sidebar-toggle').trigger('click') + find('aside.right-sidebar.right-sidebar-collapsed .js-sidebar-toggle').click find('aside.right-sidebar.right-sidebar-expanded') end end diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb index 634ea111dc1..fee8fd9b365 100644 --- a/spec/features/issues/markdown_toolbar_spec.rb +++ b/spec/features/issues/markdown_toolbar_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Issue markdown toolbar', js: true do +feature 'Issue markdown toolbar', :js do let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -16,6 +16,7 @@ feature 'Issue markdown toolbar', js: true do find('#note-body').native.send_key(:enter) find('#note-body').native.send_keys('bold') + find('.js-main-target-form #note-body') page.evaluate_script('document.querySelectorAll(".js-main-target-form #note-body")[0].setSelectionRange(4, 9)') first('.toolbar-btn').click @@ -28,6 +29,7 @@ feature 'Issue markdown toolbar', js: true do find('#note-body').native.send_key(:enter) find('#note-body').native.send_keys('underline') + find('.js-main-target-form #note-body') page.evaluate_script('document.querySelectorAll(".js-main-target-form #note-body")[0].setSelectionRange(4, 50)') find('.toolbar-btn:nth-child(2)').click diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index b2724945da4..17035b5501c 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -37,8 +37,8 @@ feature 'issue move to another project' do visit issue_path(issue) end - scenario 'moving issue to another project', js: true do - find('.js-move-issue').trigger('click') + scenario 'moving issue to another project', :js do + find('.js-move-issue').click wait_for_requests all('.js-move-issue-dropdown-item')[0].click find('.js-move-issue-confirmation-button').click @@ -49,10 +49,10 @@ feature 'issue move to another project' do expect(page.current_path).to include project_path(new_project) end - scenario 'searching project dropdown', js: true do + scenario 'searching project dropdown', :js do new_project_search.team << [user, :reporter] - find('.js-move-issue').trigger('click') + find('.js-move-issue').click wait_for_requests page.within '.js-sidebar-move-issue-block' do @@ -63,13 +63,13 @@ feature 'issue move to another project' do end end - context 'user does not have permission to move the issue to a project', js: true do + context 'user does not have permission to move the issue to a project', :js do let!(:private_project) { create(:project, :private) } let(:another_project) { create(:project) } background { another_project.team << [user, :guest] } scenario 'browsing projects in projects select' do - find('.js-move-issue').trigger('click') + find('.js-move-issue').click wait_for_requests page.within '.js-sidebar-move-issue-block' do diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 332ce78b138..d25231d624c 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'New issue', js: true do +describe 'New issue', :js do include StubENV let(:project) { create(:project, :public) } diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index 8405f1cd48d..29a2d38ae18 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Manually create a todo item from issue', js: true do +feature 'Manually create a todo item from issue', :js do let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 7437c469a72..c4c06ed514b 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Issues > User uses quick actions', js: true do +feature 'Issues > User uses quick actions', :js do include QuickActionsHelpers it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do @@ -226,7 +226,7 @@ feature 'Issues > User uses quick actions', js: true do end it 'applies the commands to both issues and moves the issue' do - write_note("/label ~#{bug.title} ~#{wontfix.title}\n/milestone %\"#{milestone.title}\"\n/move #{target_project.full_path}") + write_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}") expect(page).to have_content 'Commands applied' expect(issue.reload).to be_closed @@ -245,7 +245,7 @@ feature 'Issues > User uses quick actions', js: true do end it 'moves the issue and applies the commands to both issues' do - write_note("/move #{target_project.full_path}\n/label ~#{bug.title} ~#{wontfix.title}\n/milestone %\"#{milestone.title}\"") + write_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"") expect(page).to have_content 'Commands applied' expect(issue.reload).to be_closed diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index fb763c93c66..b9af77f918a 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -41,7 +41,7 @@ describe 'Issues' do project: project) end - it 'allows user to select unassigned', js: true do + it 'allows user to select unassigned', :js do visit edit_project_issue_path(project, issue) expect(page).to have_content "Assignee #{user.name}" @@ -59,7 +59,7 @@ describe 'Issues' do end end - describe 'due date', js: true do + describe 'due date', :js do context 'on new form' do before do visit new_project_issue_path(project) @@ -131,6 +131,14 @@ describe 'Issues' do end describe 'Issue info' do + it 'links to current issue in breadcrubs' do + issue = create(:issue, project: project) + + visit project_issue_path(project, issue) + + expect(find('.breadcrumbs-sub-title a')[:href]).to end_with(issue_path(issue)) + end + it 'excludes award_emoji from comment count' do issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar') create(:award_emoji, awardable: issue) @@ -356,10 +364,10 @@ describe 'Issues' do visit namespace_project_issues_path(user.namespace, project1) end - it 'changes incoming email address token', js: true do + it 'changes incoming email address token', :js do find('.issue-email-modal-btn').click previous_token = find('input#issue_email').value - find('.incoming-email-token-reset').trigger('click') + find('.incoming-email-token-reset').click wait_for_requests @@ -372,7 +380,7 @@ describe 'Issues' do end end - describe 'update labels from issue#show', js: true do + describe 'update labels from issue#show', :js do let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } let!(:label) { create(:label, project: project) } @@ -395,7 +403,7 @@ describe 'Issues' do let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } context 'by authorized user' do - it 'allows user to select unassigned', js: true do + it 'allows user to select unassigned', :js do visit project_issue_path(project, issue) page.within('.assignee') do @@ -413,7 +421,7 @@ describe 'Issues' do expect(issue.reload.assignees).to be_empty end - it 'allows user to select an assignee', js: true do + it 'allows user to select an assignee', :js do issue2 = create(:issue, project: project, author: user) visit project_issue_path(project, issue2) @@ -434,7 +442,7 @@ describe 'Issues' do end end - it 'allows user to unselect themselves', js: true do + it 'allows user to unselect themselves', :js do issue2 = create(:issue, project: project, author: user) visit project_issue_path(project, issue2) @@ -463,7 +471,7 @@ describe 'Issues' do project.team << [[guest], :guest] end - it 'shows assignee text', js: true do + it 'shows assignee text', :js do sign_out(:user) sign_in(guest) @@ -478,7 +486,7 @@ describe 'Issues' do let!(:milestone) { create(:milestone, project: project) } context 'by authorized user' do - it 'allows user to select unassigned', js: true do + it 'allows user to select unassigned', :js do visit project_issue_path(project, issue) page.within('.milestone') do @@ -496,7 +504,7 @@ describe 'Issues' do expect(issue.reload.milestone).to be_nil end - it 'allows user to de-select milestone', js: true do + it 'allows user to de-select milestone', :js do visit project_issue_path(project, issue) page.within('.milestone') do @@ -526,7 +534,7 @@ describe 'Issues' do issue.save end - it 'shows milestone text', js: true do + it 'shows milestone text', :js do sign_out(:user) sign_in(guest) @@ -559,7 +567,7 @@ describe 'Issues' do end end - context 'dropzone upload file', js: true do + context 'dropzone upload file', :js do before do visit new_project_issue_path(project) end @@ -575,6 +583,18 @@ describe 'Issues' do expect(page.find_field("issue_description").value).not_to match /\n\n$/ end + + it "cancels a file upload correctly" do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + + click_button 'Cancel' + end + + expect(page).to have_button('Attach a file') + expect(page).not_to have_button('Cancel') + expect(page).not_to have_selector('.uploading-progress-container', visible: true) + end end context 'form filled by URL parameters' do @@ -630,7 +650,7 @@ describe 'Issues' do end describe 'due date' do - context 'update due on issue#show', js: true do + context 'update due on issue#show', :js do let(:issue) { create(:issue, project: project, author: user, assignees: [user]) } before do @@ -674,8 +694,8 @@ describe 'Issues' do end end - describe 'title issue#show', js: true do - it 'updates the title', js: true do + describe 'title issue#show', :js do + it 'updates the title', :js do issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title') visit project_issue_path(project, issue) @@ -689,20 +709,20 @@ describe 'Issues' do end end - describe 'confidential issue#show', js: true do + describe 'confidential issue#show', :js do it 'shows confidential sibebar information as confidential and can be turned off' do issue = create(:issue, :confidential, project: project) visit project_issue_path(project, issue) - expect(page).to have_css('.confidential-issue-warning') - expect(page).to have_css('.is-confidential') - expect(page).not_to have_css('.is-not-confidential') + expect(page).to have_css('.issuable-note-warning') + expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active') + expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active') find('.confidential-edit').click - expect(page).to have_css('.confidential-warning-message') + expect(page).to have_css('.sidebar-item-warning-message') - within('.confidential-warning-message') do + within('.sidebar-item-warning-message') do find('.btn-close').click end @@ -710,7 +730,7 @@ describe 'Issues' do visit project_issue_path(project, issue) - expect(page).not_to have_css('.is-confidential') + expect(page).not_to have_css('.is-active') end end end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index c9983f0941f..6dfabcc7225 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -197,7 +197,7 @@ feature 'Login' do expect(page).to have_content('The global settings require you to enable Two-Factor Authentication for your account. You need to do this before ') end - it 'allows skipping two-factor configuration', js: true do + it 'allows skipping two-factor configuration', :js do expect(current_path).to eq profile_two_factor_auth_path click_link 'Configure it later' @@ -215,7 +215,7 @@ feature 'Login' do ) end - it 'disallows skipping two-factor configuration', js: true do + it 'disallows skipping two-factor configuration', :js do expect(current_path).to eq profile_two_factor_auth_path expect(page).not_to have_link('Configure it later') end @@ -260,7 +260,7 @@ feature 'Login' do 'before ') end - it 'allows skipping two-factor configuration', js: true do + it 'allows skipping two-factor configuration', :js do expect(current_path).to eq profile_two_factor_auth_path click_link 'Configure it later' @@ -279,7 +279,7 @@ feature 'Login' do ) end - it 'disallows skipping two-factor configuration', js: true do + it 'disallows skipping two-factor configuration', :js do expect(current_path).to eq profile_two_factor_auth_path expect(page).not_to have_link('Configure it later') end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb index 63fa72650ac..d49d145f254 100644 --- a/spec/features/merge_requests/assign_issues_spec.rb +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Merge request issue assignment', js: true do +feature 'Merge request issue assignment', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:issue1) { create(:issue, project: project) } diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb index e886309133d..a24464f2556 100644 --- a/spec/features/merge_requests/award_spec.rb +++ b/spec/features/merge_requests/award_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Merge request awards', js: true do +feature 'Merge request awards', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb index 1f5e7b55fb0..fbbfe7942be 100644 --- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb +++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Check if mergeable with unresolved discussions', js: true do +feature 'Check if mergeable with unresolved discussions', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) } diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 4b1e1b9a8d4..48f370c3ad4 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Cherry-pick Merge Requests', js: true do +describe 'Cherry-pick Merge Requests', :js do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb index 299b4f5708a..4dd4e40f52c 100644 --- a/spec/features/merge_requests/closes_issues_spec.rb +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Merge Request closing issues message', js: true do +feature 'Merge Request closing issues message', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:issue_1) { create(:issue, project: project)} diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 2d2c674f8fb..4e2963c116d 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Merge request conflict resolution', js: true do +feature 'Merge request conflict resolution', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } @@ -23,11 +23,11 @@ feature 'Merge request conflict resolution', js: true do within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do all('button', text: 'Use ours').each do |button| - button.trigger('click') + button.send_keys(:return) end end - click_button 'Commit conflict resolution' + find_button('Commit conflict resolution').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -60,16 +60,18 @@ feature 'Merge request conflict resolution', js: true do within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_requests + find('.files-wrapper .diff-file pre') execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_requests + find('.files-wrapper .diff-file pre') execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end - click_button 'Commit conflict resolution' + find_button('Commit conflict resolution').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -139,6 +141,7 @@ feature 'Merge request conflict resolution', js: true do it 'conflicts are resolved in Edit inline mode' do within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do wait_for_requests + find('.files-wrapper .diff-file pre') execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') end diff --git a/spec/features/merge_requests/create_new_mr_from_fork_spec.rb b/spec/features/merge_requests/create_new_mr_from_fork_spec.rb new file mode 100644 index 00000000000..93c40ff6443 --- /dev/null +++ b/spec/features/merge_requests/create_new_mr_from_fork_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +feature 'Creating a merge request from a fork', :js do + include ProjectForksHelper + + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let!(:source_project) do + fork_project(project, user, + repository: true, + namespace: user.namespace) + end + + before do + source_project.add_master(user) + + sign_in(user) + end + + shared_examples 'create merge request to other project' do + it 'has all possible target projects' do + visit project_new_merge_request_path(source_project) + + first('.js-target-project').click + + within('.dropdown-target-project .dropdown-content') do + expect(page).to have_content(project.full_path) + expect(page).to have_content(target_project.full_path) + expect(page).to have_content(source_project.full_path) + end + end + + it 'allows creating the merge request to another target project' do + visit project_merge_requests_path(source_project) + + page.within '.content' do + click_link 'New merge request' + end + + find('.js-source-branch', match: :first).click + find('.dropdown-source-branch .dropdown-content a', match: :first).click + + first('.js-target-project').click + find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click + + click_button 'Compare branches and continue' + + wait_for_requests + + expect { click_button 'Submit merge request' } + .to change { target_project.merge_requests.reload.size }.by(1) + end + + it 'updates the branches when selecting a new target project' do + target_project_member = target_project.owner + CreateBranchService.new(target_project, target_project_member) + .execute('a-brand-new-branch-to-test', 'master') + visit project_new_merge_request_path(source_project) + + first('.js-target-project').click + find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click + + wait_for_requests + + first('.js-target-branch').click + + within('.dropdown-target-branch .dropdown-content') do + expect(page).to have_content('a-brand-new-branch-to-test') + end + end + end + + context 'creating to the source of a fork' do + let!(:target_project) { project } + + it_behaves_like('create merge request to other project') + end + + context 'creating to a sibling of a fork' do + let!(:target_project) do + other_user = create(:user) + fork_project(project, other_user, + repository: true, + namespace: other_user.namespace) + end + + it_behaves_like('create merge request to other project') + end +end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 96e8027a54d..5402d61da54 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Create New Merge Request', js: true do +feature 'Create New Merge Request', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index 09541873f71..ca2225318cd 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -1,21 +1,20 @@ require 'spec_helper' feature 'Merge request created from fork' do + include ProjectForksHelper + given(:user) { create(:user) } given(:project) { create(:project, :public, :repository) } - given(:fork_project) { create(:project, :public, :repository) } + given(:forked_project) { fork_project(project, user, repository: true) } given!(:merge_request) do - create(:forked_project_link, forked_to_project: fork_project, - forked_from_project: project) - - create(:merge_request_with_diffs, source_project: fork_project, + create(:merge_request_with_diffs, source_project: forked_project, target_project: project, description: 'Test merge request') end background do - fork_project.team << [user, :master] + forked_project.team << [user, :master] sign_in user end @@ -31,11 +30,11 @@ feature 'Merge request created from fork' do background do create(:note_on_commit, note: comment, - project: fork_project, + project: forked_project, commit_id: merge_request.commit_shas.first) end - scenario 'user can reply to the comment', js: true do + scenario 'user can reply to the comment', :js do visit_merge_request(merge_request) expect(page).to have_content(comment) @@ -55,10 +54,10 @@ feature 'Merge request created from fork' do context 'source project is deleted' do background do MergeRequests::MergeService.new(project, user).execute(merge_request) - fork_project.destroy! + forked_project.destroy! end - scenario 'user can access merge request', js: true do + scenario 'user can access merge request', :js do visit_merge_request(merge_request) expect(page).to have_content 'Test merge request' @@ -69,7 +68,7 @@ feature 'Merge request created from fork' do context 'pipeline present in source project' do given(:pipeline) do create(:ci_pipeline, - project: fork_project, + project: forked_project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) end @@ -79,12 +78,11 @@ feature 'Merge request created from fork' do create(:ci_build, pipeline: pipeline, name: 'spinach') end - scenario 'user visits a pipelines page', js: true do + scenario 'user visits a pipelines page', :js do visit_merge_request(merge_request) page.within('.merge-request-tabs') { click_link 'Pipelines' } page.within('.ci-table') do - expect(page).to have_content pipeline.status expect(page).to have_content pipeline.id end end diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb index 874c6e2ff69..7f69e82af4c 100644 --- a/spec/features/merge_requests/deleted_source_branch_spec.rb +++ b/spec/features/merge_requests/deleted_source_branch_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # This test serves as a regression test for a bug that caused an error # message to be shown by JavaScript when the source branch was deleted. # Please do not remove "js: true". -describe 'Deleted source branch', js: true do +describe 'Deleted source branch', :js do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb index 9bcb78d5206..9e816cf041b 100644 --- a/spec/features/merge_requests/diff_notes_avatars_spec.rb +++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Diff note avatars', js: true do +feature 'Diff note avatars', :js do include NoteInteractionHelpers let(:user) { create(:user) } @@ -22,7 +22,7 @@ feature 'Diff note avatars', js: true do project.team << [user, :master] sign_in user - page.driver.set_cookie('sidebar_collapsed', 'true') + set_cookie('sidebar_collapsed', 'true') end context 'discussion tab' do @@ -56,7 +56,7 @@ feature 'Diff note avatars', js: true do end it 'does not render avatar after commenting' do - first('.diff-line-num').trigger('mouseover') + first('.diff-line-num').click find('.js-add-diff-note-button').click page.within('.js-discussion-note-form') do @@ -84,29 +84,29 @@ feature 'Diff note avatars', js: true do end it 'shows note avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) expect(page).to have_selector('img.js-diff-comment-avatar', count: 1) end end it 'shows comment on note avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}") end end it 'toggles comments when clicking avatar' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) end expect(page).to have_selector('.notes_holder', visible: false) - page.within find("[id='#{position.line_code(project.repository)}']") do + page.within find_line(position.line_code(project.repository)) do first('img.js-diff-comment-avatar').click end @@ -117,12 +117,12 @@ feature 'Diff note avatars', js: true do open_more_actions_dropdown(note) page.within find(".note-row-#{note.id}") do - find('.js-note-delete').click + accept_confirm { find('.js-note-delete').click } end wait_for_requests - page.within find("[id='#{position.line_code(project.repository)}']") do + page.within find_line(position.line_code(project.repository)) do expect(page).not_to have_selector('img.js-diff-comment-avatar') end end @@ -138,8 +138,8 @@ feature 'Diff note avatars', js: true do wait_for_requests end - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').trigger('click') + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) expect(page).to have_selector('img.js-diff-comment-avatar', count: 2) end @@ -152,14 +152,14 @@ feature 'Diff note avatars', js: true do page.within '.js-discussion-note-form' do find('.js-note-text').native.send_keys('Test') - find('.js-comment-button').trigger('click') + find('.js-comment-button').click wait_for_requests end end - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').trigger('click') + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) expect(page).to have_selector('img.js-diff-comment-avatar', count: 3) expect(find('.diff-comments-more-count')).to have_content '+1' @@ -176,8 +176,8 @@ feature 'Diff note avatars', js: true do end it 'shows extra comment count' do - page.within find("[id='#{position.line_code(project.repository)}']") do - find('.diff-notes-collapse').click + page.within find_line(position.line_code(project.repository)) do + find('.diff-notes-collapse').send_keys(:return) expect(find('.diff-comments-more-count')).to have_content '+1' end @@ -185,4 +185,10 @@ feature 'Diff note avatars', js: true do end end end + + def find_line(line_code) + line = find("[id='#{line_code}']") + line = line.find(:xpath, 'preceding-sibling::*[1][self::td]') if line.tag_name == 'td' + line + end end diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index fd110e68e84..15d380b1bf4 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Diff notes resolve', js: true do +feature 'Diff notes resolve', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } @@ -88,14 +88,43 @@ feature 'Diff notes resolve', js: true do end end - it 'hides resolved discussion' do - page.within '.diff-content' do - click_button 'Resolve discussion' + describe 'resolved discussion' do + before do + page.within '.diff-content' do + click_button 'Resolve discussion' + end + + visit_merge_request end - visit_merge_request + describe 'timeline view' do + it 'hides when resolve discussion is clicked' do + expect(page).to have_selector('.discussion-body', visible: false) + end + + it 'shows resolved discussion when toggled' do + find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click + + expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible + end + end + + describe 'side-by-side view' do + before do + page.within('.merge-request-tabs') { click_link 'Changes' } + page.find('#parallel-diff-btn').click + end + + it 'hides when resolve discussion is clicked' do + expect(page).to have_selector('.diffs .diff-file .notes_holder', visible: false) + end - expect(page).to have_selector('.discussion-body', visible: false) + it 'shows resolved discussion when toggled' do + find('.diff-comment-avatar-holders').click + + expect(find('.diffs .diff-file .notes_holder')).to be_visible + end + end end it 'allows user to resolve from reply form without a comment' do @@ -163,7 +192,7 @@ feature 'Diff notes resolve', js: true do page.find('.discussion-next-btn').click end - expect(page.evaluate_script("$('body').scrollTop()")).to be > 0 + expect(page.evaluate_script("window.pageYOffset")).to be > 0 end it 'hides jump to next button when all resolved' do @@ -212,10 +241,8 @@ feature 'Diff notes resolve', js: true do end it 'resolves discussion' do - page.all('.note').each do |note| - note.all('.line-resolve-btn').each do |button| - button.click - end + page.all('.note .line-resolve-btn').each do |button| + button.click end expect(page).to have_content('Resolved by') @@ -276,10 +303,10 @@ feature 'Diff notes resolve', js: true do end page.within '.line-resolve-all-container' do - page.find('.discussion-next-btn').trigger('click') + page.find('.discussion-next-btn').click end - expect(page.evaluate_script("$('body').scrollTop()")).to be > 0 + expect(page.evaluate_script("window.pageYOffset")).to be > 0 end it 'updates updated text after resolving note' do diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb index e9068f722d5..1bf77296ae6 100644 --- a/spec/features/merge_requests/diffs_spec.rb +++ b/spec/features/merge_requests/diffs_spec.rb @@ -1,18 +1,18 @@ require 'spec_helper' -feature 'Diffs URL', js: true do +feature 'Diffs URL', :js do + include ProjectForksHelper + let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } context 'when visit with */* as accept header' do - before do - page.driver.add_header('Accept', '*/*') - end - it 'renders the notes' do create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master' - visit diffs_project_merge_request_path(project, merge_request) + inspect_requests(inject_headers: { 'Accept' => '*/*' }) do + visit diffs_project_merge_request_path(project, merge_request) + end # Load notes and diff through AJAX expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master') @@ -64,7 +64,7 @@ feature 'Diffs URL', js: true do context 'when editing file' do let(:author_user) { create(:user) } let(:user) { create(:user) } - let(:forked_project) { Projects::ForkService.new(project, author_user).execute } + let(:forked_project) { fork_project(project, author_user, repository: true) } let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) } let(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") } @@ -88,7 +88,7 @@ feature 'Diffs URL', js: true do visit diffs_project_merge_request_path(project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax - find("[id=\"#{changelog_id}\"] .js-edit-blob").trigger('click') + find("[id=\"#{changelog_id}\"] .js-edit-blob").click expect(page).to have_selector('.js-fork-suggestion-button', count: 1) expect(page).to have_selector('.js-cancel-fork-suggestion-button', count: 1) diff --git a/spec/features/merge_requests/discussion_lock_spec.rb b/spec/features/merge_requests/discussion_lock_spec.rb new file mode 100644 index 00000000000..7bbd3b1e69e --- /dev/null +++ b/spec/features/merge_requests/discussion_lock_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe 'Discussion Lock', :js do + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: project, author: user) } + let(:project) { create(:project, :public, :repository) } + + before do + sign_in(user) + end + + context 'when the discussion is locked' do + before do + merge_request.update_attribute(:discussion_locked, true) + end + + context 'when a user is a team member' do + before do + project.add_developer(user) + visit project_merge_request_path(project, merge_request) + end + + it 'the user can create a comment' do + page.within('.issuable-discussion #notes .js-main-target-form') do + fill_in 'note[note]', with: 'Some new comment' + click_button 'Comment' + end + + wait_for_requests + + expect(find('.issuable-discussion #notes')).to have_content('Some new comment') + end + end + + context 'when a user is not a team member' do + before do + visit project_merge_request_path(project, merge_request) + end + + it 'the user can not create a comment' do + page.within('.issuable-discussion #notes') do + expect(page).not_to have_selector('js-main-target-form') + expect(page.find('.disabled-comment')) + .to have_content('This merge request is locked. Only project members can comment.') + end + end + end + end +end diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index 7386e78fb13..4362f8b3fcc 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -29,7 +29,7 @@ feature 'Edit Merge Request' do expect(page).to have_content 'Someone edited the merge request the same time you did' end - it 'allows to unselect "Remove source branch"', js: true do + it 'allows to unselect "Remove source branch"', :js do merge_request.update(merge_params: { 'force_remove_source_branch' => '1' }) expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy @@ -42,7 +42,7 @@ feature 'Edit Merge Request' do expect(page).to have_content 'Remove source branch' end - it 'should preserve description textarea height', js: true do + it 'should preserve description textarea height', :js do long_description = %q( Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ac ornare ligula, ut tempus arcu. Etiam ultricies accumsan dolor vitae faucibus. Donec at elit lacus. Mauris orci ante, aliquam quis lorem eget, convallis faucibus arcu. Aenean at pulvinar lacus. Ut viverra quam massa, molestie ornare tortor dignissim a. Suspendisse tristique pellentesque tellus, id lacinia metus elementum id. Nam tristique, arcu rhoncus faucibus viverra, lacus ipsum sagittis ligula, vitae convallis odio lacus a nibh. Ut tincidunt est purus, ac vestibulum augue maximus in. Suspendisse vel erat et mi ultricies semper. Pellentesque volutpat pellentesque consequat. @@ -66,6 +66,7 @@ feature 'Edit Merge Request' do end def get_textarea_height + find('#merge_request_description') page.evaluate_script('document.getElementById("merge_request_description").offsetHeight') end end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index 166c02a7a7f..8b9ff9be943 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -18,7 +18,7 @@ feature 'Merge Request filtering by Milestone' do sign_in(user) end - scenario 'filters by no Milestone', js: true do + scenario 'filters by no Milestone', :js do create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :simple, source_project: project, milestone: milestone) @@ -32,7 +32,7 @@ feature 'Merge Request filtering by Milestone' do expect(page).to have_css('.merge-request', count: 1) end - context 'filters by upcoming milestone', js: true do + context 'filters by upcoming milestone', :js do it 'does not show merge requests with no expiry' do create(:merge_request, :with_diffs, source_project: project) create(:merge_request, :simple, source_project: project, milestone: milestone) @@ -67,7 +67,7 @@ feature 'Merge Request filtering by Milestone' do end end - scenario 'filters by a specific Milestone', js: true do + scenario 'filters by a specific Milestone', :js do create(:merge_request, :with_diffs, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project) @@ -83,7 +83,7 @@ feature 'Merge Request filtering by Milestone' do milestone.update(name: "rock 'n' roll") end - scenario 'filters by a specific Milestone', js: true do + scenario 'filters by a specific Milestone', :js do create(:merge_request, :with_diffs, source_project: project, milestone: milestone) create(:merge_request, :simple, source_project: project) diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb index 16703bc1c01..aac295ab940 100644 --- a/spec/features/merge_requests/filter_merge_requests_spec.rb +++ b/spec/features/merge_requests/filter_merge_requests_spec.rb @@ -36,7 +36,7 @@ describe 'Filter merge requests' do expect_mr_list_count(0) end - context 'assignee', js: true do + context 'assignee', :js do it 'updates to current user' do expect_assignee_visual_tokens() end @@ -69,7 +69,7 @@ describe 'Filter merge requests' do expect_mr_list_count(0) end - context 'milestone', js: true do + context 'milestone', :js do it 'updates to current milestone' do expect_milestone_visual_tokens() end @@ -88,7 +88,7 @@ describe 'Filter merge requests' do end end - describe 'for label from mr#index', js: true do + describe 'for label from mr#index', :js do it 'filters by no label' do input_filtered_search('label:none') @@ -137,7 +137,7 @@ describe 'Filter merge requests' do expect_mr_list_count(0) end - context 'assignee and label', js: true do + context 'assignee and label', :js do def expect_assignee_label_visual_tokens wait_for_requests @@ -183,7 +183,7 @@ describe 'Filter merge requests' do visit project_merge_requests_path(project) end - context 'only text', js: true do + context 'only text', :js do it 'filters merge requests by searched text' do input_filtered_search('bug') @@ -199,7 +199,7 @@ describe 'Filter merge requests' do end end - context 'filters and searches', js: true do + context 'filters and searches', :js do it 'filters by text and label' do input_filtered_search('Bug') @@ -289,7 +289,7 @@ describe 'Filter merge requests' do end end - describe 'filter by assignee id', js: true do + describe 'filter by assignee id', :js do it 'filter by current user' do visit project_merge_requests_path(project, assignee_id: user.id) @@ -312,7 +312,7 @@ describe 'Filter merge requests' do end end - describe 'filter by author id', js: true do + describe 'filter by author id', :js do it 'filter by current user' do visit project_merge_requests_path(project, author_id: user.id) diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index de98b147d04..1dcc1e139a0 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -1,8 +1,10 @@ require 'rails_helper' describe 'New/edit merge request', :js do + include ProjectForksHelper + let!(:project) { create(:project, :public, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } + let(:forked_project) { fork_project(project, nil, repository: true) } let!(:user) { create(:user) } let!(:user2) { create(:user) } let!(:milestone) { create(:milestone, project: project) } @@ -41,7 +43,7 @@ describe 'New/edit merge request', :js do expect(page).to have_content user2.name end - find('a', text: 'Assign to me').trigger('click') + find('a', text: 'Assign to me').click expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name @@ -170,16 +172,16 @@ describe 'New/edit merge request', :js do context 'forked project' do before do - fork_project.team << [user, :master] + forked_project.team << [user, :master] sign_in(user) end context 'new merge request' do before do visit project_new_merge_request_path( - fork_project, + forked_project, merge_request: { - source_project_id: fork_project.id, + source_project_id: forked_project.id, target_project_id: project.id, source_branch: 'fix', target_branch: 'master' @@ -238,7 +240,7 @@ describe 'New/edit merge request', :js do context 'edit merge request' do before do merge_request = create(:merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project, source_branch: 'fix', target_branch: 'master' diff --git a/spec/features/merge_requests/image_diff_notes.rb b/spec/features/merge_requests/image_diff_notes.rb new file mode 100644 index 00000000000..3c53b51e330 --- /dev/null +++ b/spec/features/merge_requests/image_diff_notes.rb @@ -0,0 +1,196 @@ +require 'spec_helper' + +feature 'image diff notes', :js do + include NoteInteractionHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + + before do + project.team << [user, :master] + sign_in user + + page.driver.set_cookie('sidebar_collapsed', 'true') + + # Stub helper to return any blob file as image from public app folder. + # This is necessary to run this specs since we don't display repo images in capybara. + allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_path).and_return('/apple-touch-icon.png') + end + + context 'create commit diff notes' do + commit_id = '2f63565e7aac07bcdadb654e253078b727143ec4' + + describe 'create a new diff note' do + before do + visit project_commit_path(project, commit_id) + create_image_diff_note + end + + it 'shows indicator badge on image diff' do + indicator = find('.js-image-badge') + + expect(indicator).to have_content('1') + end + + it 'shows the avatar badge on the new note' do + badge = find('.image-diff-avatar-link .badge') + + expect(badge).to have_content('1') + end + + it 'allows collapsing/expanding the discussion notes' do + find('.js-diff-notes-toggle', :first).click + + expect(page).not_to have_content('image diff test comment') + + find('.js-diff-notes-toggle').click + + expect(page).to have_content('image diff test comment') + end + end + + describe 'render commit diff notes' do + let(:path) { "files/images/6049019_460s.jpg" } + let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } + + let(:note1_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x: 10, + y: 10, + position_type: "image", + diff_refs: commit.diff_refs + ) + end + + let(:note2_position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x: 20, + y: 20, + position_type: "image", + diff_refs: commit.diff_refs + ) + end + + let!(:note1) { create(:diff_note_on_commit, commit_id: commit.id, project: project, position: note1_position, note: 'my note 1') } + let!(:note2) { create(:diff_note_on_commit, commit_id: commit.id, project: project, position: note2_position, note: 'my note 2') } + + before do + visit project_commit_path(project, commit.id) + wait_for_requests + end + + it 'render diff indicators within the image diff frame' do + expect(page).to have_css('.js-image-badge', count: 2) + end + + it 'shows the diff notes' do + expect(page).to have_css('.diff-content .note', count: 2) + end + + it 'shows the diff notes with correct avatar badge numbers' do + expect(page).to have_css('.image-diff-avatar-link', text: 1) + expect(page).to have_css('.image-diff-avatar-link', text: 2) + end + end + end + + %w(inline parallel).each do |view| + context "#{view} view" do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } + let(:path) { "files/images/ee_repo_logo.png" } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x: 1, + y: 1, + position_type: "image", + diff_refs: merge_request.diff_refs + ) + end + + let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } + + describe 'creating a new diff note' do + before do + visit diffs_project_merge_request_path(project, merge_request, view: view) + create_image_diff_note + end + + it 'shows indicator badge on image diff' do + indicator = find('.js-image-badge', match: :first) + + expect(indicator).to have_content('1') + end + + it 'shows the avatar badge on the new note' do + badge = find('.image-diff-avatar-link .badge', match: :first) + + expect(badge).to have_content('1') + end + + it 'allows expanding/collapsing the discussion notes' do + page.all('.js-diff-notes-toggle')[0].trigger('click') + page.all('.js-diff-notes-toggle')[1].trigger('click') + + expect(page).not_to have_content('image diff test comment') + + page.all('.js-diff-notes-toggle')[0].trigger('click') + page.all('.js-diff-notes-toggle')[1].trigger('click') + + expect(page).to have_content('image diff test comment') + end + end + end + end + + describe 'discussion tab polling', :js do + let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } + let(:path) { "files/images/ee_repo_logo.png" } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 100, + height: 100, + x: 50, + y: 50, + position_type: "image", + diff_refs: merge_request.diff_refs + ) + end + + before do + visit project_merge_request_path(project, merge_request) + end + + it 'render diff indicators within the image frame' do + diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) + + wait_for_requests + + expect(page).to have_selector('.image-comment-badge') + expect(page).to have_content(diff_note.note) + end + end +end + +def create_image_diff_note + find('.js-add-image-diff-note-button', match: :first).click + page.all('.js-add-image-diff-note-button')[0].trigger('click') + find('.diff-content .note-textarea').native.send_keys('image diff test comment') + click_button 'Comment' + wait_for_requests +end diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb index 08a3bb84aac..82b2b56ef80 100644 --- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb +++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Clicking toggle commit message link', js: true do +feature 'Clicking toggle commit message link', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:issue_1) { create(:issue, project: project)} diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb index dcc70338d7f..bac56270362 100644 --- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb +++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb @@ -52,10 +52,12 @@ feature 'Mini Pipeline Graph', :js do end it 'should expand when hovered' do + find('.mini-pipeline-graph-dropdown-toggle') before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();") toggle.hover + find('.mini-pipeline-graph-dropdown-toggle') after_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();") expect(before_width).to be < after_width @@ -90,7 +92,7 @@ feature 'Mini Pipeline Graph', :js do end it 'should close when toggle is clicked again' do - toggle.trigger('click') + toggle.click expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu') end diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 59e67420333..91f207bd339 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Only allow merge requests to be merged if the pipeline succeeds', js: true do +feature 'Only allow merge requests to be merged if the pipeline succeeds', :js do let(:merge_request) { create(:merge_request_with_diffs) } let(:project) { merge_request.target_project } @@ -10,7 +10,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t project.team << [merge_request.author, :master] end - context 'project does not have CI enabled', js: true do + context 'project does not have CI enabled', :js do it 'allows MR to be merged' do visit_merge_request(merge_request) @@ -20,7 +20,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', js: t end end - context 'when project has CI enabled', js: true do + context 'when project has CI enabled', :js do given!(:pipeline) do create(:ci_empty_pipeline, project: project, diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb index 347ce788b36..a3fcc27cab0 100644 --- a/spec/features/merge_requests/pipelines_spec.rb +++ b/spec/features/merge_requests/pipelines_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Pipelines for Merge Requests', js: true do +feature 'Pipelines for Merge Requests', :js do describe 'pipeline tab' do given(:user) { create(:user) } given(:merge_request) { create(:merge_request) } diff --git a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb index 55a82bdf2b9..25abbb469ab 100644 --- a/spec/features/merge_requests/resolve_outdated_diff_discussions.rb +++ b/spec/features/merge_requests/resolve_outdated_diff_discussions.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Resolve outdated diff discussions', js: true do +feature 'Resolve outdated diff discussions', :js do let(:project) { create(:project, :repository, :public) } let(:merge_request) do diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb index 9bbf2610bcb..bce36e05e57 100644 --- a/spec/features/merge_requests/target_branch_spec.rb +++ b/spec/features/merge_requests/target_branch_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Target branch', js: true do +describe 'Target branch', :js do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb index dd989fd49b2..fa3d988b27a 100644 --- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb +++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Toggle Whitespace Changes', js: true do +feature 'Toggle Whitespace Changes', :js do before do sign_in(create(:admin)) merge_request = create(:merge_request) diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb index 4e5ec9fbd2d..cd92ad22267 100644 --- a/spec/features/merge_requests/toggler_behavior_spec.rb +++ b/spec/features/merge_requests/toggler_behavior_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'toggler_behavior', js: true do +feature 'toggler_behavior', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project, author: user) } diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb index e6dc284cba7..c5498563b39 100644 --- a/spec/features/merge_requests/update_merge_requests_spec.rb +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -10,7 +10,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do sign_in(user) end - context 'status', js: true do + context 'status', :js do describe 'close merge request' do before do visit project_merge_requests_path(project) @@ -37,7 +37,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do end end - context 'assignee', js: true do + context 'assignee', :js do describe 'set assignee' do before do visit project_merge_requests_path(project) @@ -67,7 +67,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do end end - context 'milestone', js: true do + context 'milestone', :js do let(:milestone) { create(:milestone, project: project) } describe 'set milestone' do diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb index 2fb6d0b965f..d44eb23d7f4 100644 --- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb @@ -1,12 +1,14 @@ require 'spec_helper' feature 'Merge requests > User posts diff notes', :js do + include MergeRequestDiffHelpers + let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } let(:project) { merge_request.source_project } before do - page.driver.set_cookie('sidebar_collapsed', 'true') + set_cookie('sidebar_collapsed', 'true') project.add_developer(user) sign_in(user) @@ -101,7 +103,10 @@ feature 'Merge requests > User posts diff notes', :js do it 'allows commenting' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) - first('.js-note-delete', visible: false).trigger('click') + accept_confirm do + first('button.more-actions-toggle').click + first('.js-note-delete').click + end should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) end @@ -225,6 +230,7 @@ feature 'Merge requests > User posts diff notes', :js do write_comment_on_line(line_holder, diff_side) click_button 'Comment' + wait_for_requests assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset) @@ -233,7 +239,7 @@ feature 'Merge requests > User posts diff notes', :js do def should_allow_dismissing_a_comment(line_holder, diff_side = nil) write_comment_on_line(line_holder, diff_side) - find('.js-close-discussion-note-form').trigger('click') + find('.js-close-discussion-note-form').click assert_comment_dismissal(line_holder) end @@ -244,36 +250,6 @@ feature 'Merge requests > User posts diff notes', :js do expect(line[:num]).not_to have_css comment_button_class end - def get_line_components(line_holder, diff_side = nil) - if diff_side.nil? - get_inline_line_components(line_holder) - else - get_parallel_line_components(line_holder, diff_side) - end - end - - def get_inline_line_components(line_holder) - { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } - end - - def get_parallel_line_components(line_holder, diff_side = nil) - side_index = diff_side == 'left' ? 0 : 1 - # Wait for `.line_content` - line_holder.find('.line_content', match: :first) - # Wait for `.diff-line-num` - line_holder.find('.diff-line-num', match: :first) - { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } - end - - def click_diff_line(line_holder, diff_side = nil) - line = get_line_components(line_holder, diff_side) - line[:content].hover - - expect(line[:num]).to have_css comment_button_class - - line[:num].find(comment_button_class).trigger 'click' - end - def write_comment_on_line(line_holder, diff_side) click_diff_line(line_holder, diff_side) diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb index d7cda73ab40..f4c75a2f265 100644 --- a/spec/features/merge_requests/user_posts_notes_spec.rb +++ b/spec/features/merge_requests/user_posts_notes_spec.rb @@ -141,7 +141,7 @@ describe 'Merge requests > User posts notes', :js do end it 'removes the attachment div and resets the edit form' do - find('.js-note-attachment-delete').click + accept_confirm { find('.js-note-attachment-delete').click } is_expected.not_to have_css('.note-attachment') is_expected.not_to have_css('.current-note-edit-form') wait_for_requests diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 95c50df1896..ee0766f1192 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Merge Requests > User uses quick actions', js: true do +feature 'Merge Requests > User uses quick actions', :js do include QuickActionsHelpers it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb index 8e231fbc281..29f95039af8 100644 --- a/spec/features/merge_requests/versions_spec.rb +++ b/spec/features/merge_requests/versions_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Merge Request versions', js: true do +feature 'Merge Request versions', :js do let(:merge_request) { create(:merge_request, importing: true) } let(:project) { merge_request.source_project } let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } @@ -67,8 +67,8 @@ feature 'Merge Request versions', js: true do line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_2_2' page.within(diff_file_selector) do - find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover' - find(".line_holder[id='#{line_code}'] button").trigger 'click' + find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover + find(".line_holder[id='#{line_code}'] button").click page.within("form[data-line-code='#{line_code}']") do fill_in "note[note]", with: "Typo, please fix" @@ -137,8 +137,8 @@ feature 'Merge Request versions', js: true do line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_4' page.within(diff_file_selector) do - find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover' - find(".line_holder[id='#{line_code}'] button").trigger 'click' + find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover + find(".line_holder[id='#{line_code}'] button").click page.within("form[data-line-code='#{line_code}']") do fill_in "note[note]", with: "Typo, please fix" diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index c0221525c9f..72a52c979b3 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Widget Deployments Header', js: true do +feature 'Widget Deployments Header', :js do describe 'when deployed to an environment' do given(:user) { create(:user) } given(:project) { merge_request.target_project } @@ -42,7 +42,7 @@ feature 'Widget Deployments Header', js: true do end scenario 'does start build when stop button clicked' do - click_button('Stop environment') + accept_confirm { click_button('Stop environment') } expect(page).to have_content('close_app') end diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 443b596b3c6..2bad3b02250 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -3,10 +3,13 @@ require 'rails_helper' describe 'Merge request', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } + let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) } let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request_in_only_mwps_project) { create(:merge_request, source_project: project_only_mwps) } before do - project.team << [user, :master] + project.add_master(user) + project_only_mwps.add_master(user) sign_in(user) end @@ -160,6 +163,20 @@ describe 'Merge request', :js do end end + context 'view merge request in project with only-mwps setting enabled but no CI is setup' do + before do + visit project_merge_request_path(project_only_mwps, merge_request_in_only_mwps_project) + end + + it 'should be allowed to merge' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_requests + + expect(page).to have_selector('.accept-merge-request') + expect(find('.accept-merge-request')['disabled']).not_to be(true) + end + end + context 'view merge request with MWPS enabled but automatically merge fails' do before do merge_request.update( @@ -202,6 +219,28 @@ describe 'Merge request', :js do end end + context 'view merge request where fast-forward merge is not possible' do + before do + project.update(merge_requests_ff_only_enabled: true) + + merge_request.update( + merge_user: merge_request.author, + merge_status: :cannot_be_merged + ) + + visit project_merge_request_path(project, merge_request) + end + + it 'shows information about the merge error' do + # Wait for the `ci_status` and `merge_check` requests + wait_for_requests + + page.within('.mr-widget-body') do + expect(page).to have_content('Fast-forward merge is not possible') + end + end + end + context 'merge error' do before do allow_any_instance_of(Repository).to receive(:merge).and_return(false) @@ -217,7 +256,7 @@ describe 'Merge request', :js do end end - context 'user can merge into source project but cannot push to fork', js: true do + context 'user can merge into source project but cannot push to fork', :js do let(:fork_project) { create(:project, :public, :repository) } let(:user2) { create(:user) } diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index f183dd8cb75..c60883911f7 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Profile account page' do +describe 'Profile account page', :js do let(:user) { create(:user) } before do @@ -12,55 +12,82 @@ describe 'Profile account page' do visit profile_account_path end - it { expect(page).to have_content('Remove account') } + it { expect(page).to have_content('Delete account') } - it 'deletes the account' do - expect { click_link 'Delete account' }.to change { User.where(id: user.id).count }.by(-1) - expect(current_path).to eq(new_user_session_path) + it 'does not immediately delete the account' do + click_button 'Delete account' + + expect(User.exists?(user.id)).to be_truthy end - end - describe 'when I reset private token' do - before do - visit profile_account_path + it 'deletes user', :js do + click_button 'Delete account' + + fill_in 'password', with: '12345678' + + page.within '.popup-dialog' do + click_button 'Delete account' + end + + expect(page).to have_content('Account scheduled for removal') + expect(User.exists?(user.id)).to be_falsy + end + + it 'shows invalid password flash message', :js do + click_button 'Delete account' + + fill_in 'password', with: 'testing123' + + page.within '.popup-dialog' do + click_button 'Delete account' + end + + expect(page).to have_content('Invalid password') end - it 'resets private token' do - previous_token = find("#private-token").value + it 'does not show delete button when user owns a group' do + group = create(:group) + group.add_owner(user) - click_link('Reset private token') + visit profile_account_path - expect(find('#private-token').value).not_to eq(previous_token) + expect(page).not_to have_button('Delete account') + expect(page).to have_content("Your account is currently an owner in these groups: #{group.name}") end end describe 'when I reset RSS token' do before do - visit profile_account_path + visit profile_personal_access_tokens_path end it 'resets RSS token' do - previous_token = find("#rss-token").value + within('.rss-token-reset') do + previous_token = find("#rss_token").value - click_link('Reset RSS token') + accept_confirm { click_link('reset it') } + + expect(find('#rss_token').value).not_to eq(previous_token) + end expect(page).to have_content 'RSS token was successfully reset' - expect(find('#rss-token').value).not_to eq(previous_token) end end describe 'when I reset incoming email token' do before do allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) - visit profile_account_path + visit profile_personal_access_tokens_path end it 'resets incoming email token' do - previous_token = find('#incoming-email-token').value + within('.incoming-email-token-reset') do + previous_token = find('#incoming_email_token').value - click_link('Reset incoming email token') + accept_confirm { click_link('reset it') } - expect(find('#incoming-email-token').value).not_to eq(previous_token) + expect(find('#incoming_email_token').value).not_to eq(previous_token) + end end end diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb index 35793539e0e..5c959acbbc9 100644 --- a/spec/features/profiles/chat_names_spec.rb +++ b/spec/features/profiles/chat_names_spec.rb @@ -33,7 +33,7 @@ feature 'Profile > Chat' do scenario 'second use of link is denied' do visit authorize_path - expect(page).to have_http_status(:not_found) + expect(page).to have_gitlab_http_status(:not_found) end end @@ -51,7 +51,7 @@ feature 'Profile > Chat' do scenario 'second use of link is denied' do visit authorize_path - expect(page).to have_http_status(:not_found) + expect(page).to have_gitlab_http_status(:not_found) end end end diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb new file mode 100644 index 00000000000..11cc8aae6f3 --- /dev/null +++ b/spec/features/profiles/emails_spec.rb @@ -0,0 +1,71 @@ +require 'rails_helper' + +feature 'Profile > Emails' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + describe 'User adds an email' do + before do + visit profile_emails_path + end + + scenario 'saves the new email' do + fill_in('Email', with: 'my@email.com') + click_button('Add email address') + + expect(page).to have_content('my@email.com Unverified') + expect(page).to have_content("#{user.email} Verified") + expect(page).to have_content('Resend confirmation email') + end + + scenario 'does not add a duplicate email' do + fill_in('Email', with: user.email) + click_button('Add email address') + + email = user.emails.find_by(email: user.email) + expect(email).to be_nil + expect(page).to have_content('Email has already been taken') + end + end + + scenario 'User removes email' do + user.emails.create(email: 'my@email.com') + visit profile_emails_path + expect(page).to have_content("my@email.com") + + click_link('Remove') + expect(page).not_to have_content("my@email.com") + end + + scenario 'User confirms email' do + email = user.emails.create(email: 'my@email.com') + visit profile_emails_path + expect(page).to have_content("#{email.email} Unverified") + + email.confirm + expect(email.confirmed?).to be_truthy + + visit profile_emails_path + expect(page).to have_content("#{email.email} Verified") + end + + scenario 'User re-sends confirmation email' do + email = user.emails.create(email: 'my@email.com') + visit profile_emails_path + + expect { click_link("Resend confirmation email") }.to change { ActionMailer::Base.deliveries.size } + expect(page).to have_content("Confirmation email sent to #{email.email}") + end + + scenario 'old unconfirmed emails show Send Confirmation button' do + email = user.emails.create(email: 'my@email.com') + email.update_attribute(:confirmation_sent_at, nil) + visit profile_emails_path + + expect(page).not_to have_content('Resend confirmation email') + expect(page).to have_content('Send confirmation email') + end +end diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb index 623e4f341c5..59233e92f93 100644 --- a/spec/features/profiles/gpg_keys_spec.rb +++ b/spec/features/profiles/gpg_keys_spec.rb @@ -4,7 +4,7 @@ feature 'Profile > GPG Keys' do let(:user) { create(:user, email: GpgHelpers::User2.emails.first) } before do - login_as(user) + sign_in(user) end describe 'User adds a key' do @@ -20,6 +20,18 @@ feature 'Profile > GPG Keys' do expect(page).to have_content('bette.cartwright@example.net Unverified') expect(page).to have_content(GpgHelpers::User2.fingerprint) end + + scenario 'with multiple subkeys' do + fill_in('Key', with: GpgHelpers::User3.public_key) + click_button('Add key') + + expect(page).to have_content('john.doe@example.com Unverified') + expect(page).to have_content(GpgHelpers::User3.fingerprint) + + GpgHelpers::User3.subkey_fingerprints.each do |fingerprint| + expect(page).to have_content(fingerprint) + end + end end scenario 'User sees their key' do diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index aa71c4dbba4..7d5ba3a7328 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -12,7 +12,7 @@ feature 'Profile > SSH Keys' do visit profile_keys_path end - scenario 'auto-populates the title', js: true do + scenario 'auto-populates the title', :js do fill_in('Key', with: attributes_for(:key).fetch(:key)) expect(page).to have_field("Title", with: "dummy@gitlab.com") diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb index 45f78444362..d1edeef8da4 100644 --- a/spec/features/profiles/oauth_applications_spec.rb +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -7,14 +7,14 @@ describe 'Profile > Applications' do sign_in(user) end - describe 'User manages applications', js: true do + describe 'User manages applications', :js do it 'deletes an application' do create(:oauth_application, owner: user) visit oauth_applications_path page.within('.oauth-applications') do expect(page).to have_content('Your applications (1)') - click_button 'Destroy' + accept_confirm { click_button 'Destroy' } end expect(page).to have_content('The application was deleted successfully') @@ -28,7 +28,7 @@ describe 'Profile > Applications' do page.within('.oauth-authorized-applications') do expect(page).to have_content('Authorized applications (1)') - click_button 'Revoke' + accept_confirm { click_button 'Revoke' } end expect(page).to have_content('The application was revoked access.') diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb index 225d4c16841..fb4355074df 100644 --- a/spec/features/profiles/password_spec.rb +++ b/spec/features/profiles/password_spec.rb @@ -58,7 +58,7 @@ describe 'Profile > Password' do visit edit_profile_password_path - expect(page).to have_http_status(200) + expect(page).to have_gitlab_http_status(200) end end @@ -68,7 +68,7 @@ describe 'Profile > Password' do it 'renders 404' do visit edit_profile_password_path - expect(page).to have_http_status(404) + expect(page).to have_gitlab_http_status(404) end end end diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index f3124bbf29e..8461cd0027c 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Profile > Personal Access Tokens', js: true do +describe 'Profile > Personal Access Tokens', :js do let(:user) { create(:user) } def active_personal_access_tokens @@ -34,7 +34,7 @@ describe 'Profile > Personal Access Tokens', js: true do fill_in "Name", with: name # Set date to 1st of next month - find_field("Expires at").trigger('focus') + find_field("Expires at").click find(".pika-next").click click_on "1" @@ -78,7 +78,7 @@ describe 'Profile > Personal Access Tokens', js: true do it "allows revocation of an active token" do visit profile_personal_access_tokens_path - click_on "Revoke" + accept_confirm { click_on "Revoke" } expect(page).to have_selector(".settings-message") expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.") @@ -100,7 +100,7 @@ describe 'Profile > Personal Access Tokens', js: true do errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") } allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors) - click_on "Revoke" + accept_confirm { click_on "Revoke" } expect(active_personal_access_tokens).to have_text(personal_access_token.name) expect(page).to have_content("Could not revoke") end diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb index 6a4173d43e1..d5fe5bdffc5 100644 --- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb +++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Profile > Notifications > User changes notified_of_own_activity setting', js: true do +feature 'Profile > Notifications > User changes notified_of_own_activity setting', :js do let(:user) { create(:user) } before do diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb index 48c1787c8b7..df89918f17a 100644 --- a/spec/features/profiles/user_visits_notifications_tab_spec.rb +++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'User visits the notifications tab', js: true do +feature 'User visits the notifications tab', :js do let(:project) { create(:project) } let(:user) { create(:user) } @@ -13,7 +13,7 @@ feature 'User visits the notifications tab', js: true do it 'changes the project notifications setting' do expect(page).to have_content('Notifications') - first('#notifications-button').trigger('click') + first('#notifications-button').click click_link('On mention') expect(page).to have_content('On mention') diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index 924ee0e4174..90d6841af0e 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -53,7 +53,7 @@ describe 'User visits the profile preferences page' do expect(page).to have_content("You don't have starred projects yet") expect(page.current_path).to eq starred_dashboard_projects_path - find('.shortcuts-activity').trigger('click') + find('.shortcuts-activity').click expect(page).not_to have_content("You don't have starred projects yet") expect(page.current_path).to eq dashboard_projects_path diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb index 42b47cb3301..cb69aff8d5f 100644 --- a/spec/features/projects/artifacts/browse_spec.rb +++ b/spec/features/projects/artifacts/browse_spec.rb @@ -4,16 +4,15 @@ feature 'Browse artifact', :js do let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } + let(:browse_url) do + browse_path('other_artifacts_0.1.2') + end def browse_path(path) browse_project_job_artifacts_path(project, job, path) end context 'when visiting old URL' do - let(:browse_url) do - browse_path('other_artifacts_0.1.2') - end - before do visit browse_url.sub('/-/jobs', '/builds') end @@ -22,4 +21,47 @@ feature 'Browse artifact', :js do expect(page.current_path).to eq(browse_url) end end + + context 'when browsing a directory with an text file' do + let(:txt_entry) { job.artifacts_metadata_entry('other_artifacts_0.1.2/doc_sample.txt') } + + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) + end + + context 'when the project is public' do + it "shows external link icon and styles" do + visit browse_url + + link = first('.tree-item-file-external-link') + + expect(page).to have_link('doc_sample.txt', href: file_project_job_artifacts_path(project, job, path: txt_entry.blob.path)) + expect(link[:target]).to eq('_blank') + expect(link[:rel]).to include('noopener') + expect(link[:rel]).to include('noreferrer') + expect(page).to have_selector('.js-artifact-tree-external-icon') + end + end + + context 'when the project is private' do + let!(:private_project) { create(:project, :private) } + let(:pipeline) { create(:ci_empty_pipeline, project: private_project) } + let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) } + let(:user) { create(:user) } + + before do + private_project.add_developer(user) + + sign_in(user) + end + + it 'shows internal link styles' do + visit browse_project_job_artifacts_path(private_project, job, 'other_artifacts_0.1.2') + + expect(page).to have_link('doc_sample.txt') + expect(page).not_to have_selector('.js-artifact-tree-external-icon') + end + end + end end diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb index f1bdb2812c6..6f76c14910b 100644 --- a/spec/features/projects/artifacts/download_spec.rb +++ b/spec/features/projects/artifacts/download_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Download artifact', :js do +feature 'Download artifact' do let(:project) { create(:project, :public) } let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) } let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) } diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb index b2be10a7e0c..df1d17bdcb7 100644 --- a/spec/features/projects/artifacts/file_spec.rb +++ b/spec/features/projects/artifacts/file_spec.rb @@ -39,7 +39,6 @@ feature 'Artifact file', :js do context 'JPG file' do before do - page.driver.browser.url_blacklist = [] visit_file('rails_sample.jpg') wait_for_requests diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 368a046f741..c68e10a2563 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -50,7 +50,7 @@ feature 'test coverage badge' do scenario 'user requests test coverage badge image' do show_test_coverage_badge - expect(page).to have_http_status(404) + expect(page).to have_gitlab_http_status(404) end end diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 89ae891037e..68c4a647958 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -39,7 +39,7 @@ feature 'list of badges' do end end - scenario 'user changes current ref of build status badge', js: true do + scenario 'user changes current ref of build status badge', :js do page.within('.pipeline-status') do first('.js-project-refs-dropdown').click diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb index 1160f674974..c12e56d2c3f 100644 --- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb +++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', js: true do +feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do include TreeHelper let(:project) { create(:project, :public, :repository) } diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 62ac9fd0e95..965028a6f90 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Editing file blob', js: true do +feature 'Editing file blob', :js do include TreeHelper let(:project) { create(:project, :public, :repository) } @@ -20,6 +20,7 @@ feature 'Editing file blob', js: true do def edit_and_commit wait_for_requests find('.js-edit-blob').click + find('#editor') execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")') click_button 'Commit changes' end diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb index 1e3080fa319..9f1fef80ab5 100644 --- a/spec/features/projects/blobs/shortcuts_blob_spec.rb +++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb @@ -6,7 +6,7 @@ feature 'Blob shortcuts' do let(:path) { project.repository.ls_files(project.repository.root_ref)[0] } let(:sha) { project.repository.commit.sha } - describe 'On a file(blob)', js: true do + describe 'On a file(blob)', :js do def get_absolute_url(path = "") "http://#{page.server.host}:#{page.server.port}#{path}" end diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index ad06cee4e81..2f407b13c2f 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do describe 'when checking branches' do context 'with artifacts' do before do - visit project_branches_path(project) + visit project_branches_path(project, search: 'binary-encoding') end scenario 'shows download artifacts button' do diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index ad4527a0b74..7a77df83034 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -5,12 +5,6 @@ describe 'Branches' do let(:project) { create(:project, :public, :repository) } let(:repository) { project.repository } - def set_protected_branch_name(branch_name) - find(".js-protected-branch-select").click - find(".dropdown-input-field").set(branch_name) - click_on("Create wildcard #{branch_name}") - end - context 'logged in as developer' do before do sign_in(user) @@ -18,12 +12,10 @@ describe 'Branches' do end describe 'Initial branches page' do - it 'shows all the branches' do + it 'shows all the branches sorted by last updated by default' do visit project_branches_path(project) - repository.branches_sorted_by(:name).first(20).each do |branch| - expect(page).to have_content("#{branch.name}") - end + expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc)) end it 'sorts the branches by name' do @@ -32,22 +24,7 @@ describe 'Branches' do click_button "Last updated" # Open sorting dropdown click_link "Name" - sorted = repository.branches_sorted_by(:name).first(20).map do |branch| - Regexp.escape(branch.name) - end - expect(page).to have_content(/#{sorted.join(".*")}/) - end - - it 'sorts the branches by last updated' do - visit project_branches_path(project) - - click_button "Last updated" # Open sorting dropdown - click_link "Last updated" - - sorted = repository.branches_sorted_by(:updated_desc).first(20).map do |branch| - Regexp.escape(branch.name) - end - expect(page).to have_content(/#{sorted.join(".*")}/) + expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name)) end it 'sorts the branches by oldest updated' do @@ -56,10 +33,7 @@ describe 'Branches' do click_button "Last updated" # Open sorting dropdown click_link "Oldest updated" - sorted = repository.branches_sorted_by(:updated_asc).first(20).map do |branch| - Regexp.escape(branch.name) - end - expect(page).to have_content(/#{sorted.join(".*")}/) + expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc)) end it 'avoids a N+1 query in branches index' do @@ -72,7 +46,7 @@ describe 'Branches' do end describe 'Find branches' do - it 'shows filtered branches', js: true do + it 'shows filtered branches', :js do visit project_branches_path(project) fill_in 'branch-search', with: 'fix' @@ -84,7 +58,7 @@ describe 'Branches' do end describe 'Delete unprotected branch' do - it 'removes branch after confirmation', js: true do + it 'removes branch after confirmation', :js do visit project_branches_path(project) fill_in 'branch-search', with: 'fix' @@ -93,34 +67,12 @@ describe 'Branches' do expect(page).to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 1) - find('.js-branch-fix .btn-remove').trigger(:click) + accept_confirm { find('.js-branch-fix .btn-remove').click } expect(page).not_to have_content('fix') expect(find('.all-branches')).to have_selector('li', count: 0) end end - - describe 'Delete protected branch' do - before do - project.add_user(user, :master) - visit project_protected_branches_path(project) - set_protected_branch_name('fix') - click_on "Protect" - - within(".protected-branches-list") { expect(page).to have_content('fix') } - expect(ProtectedBranch.count).to eq(1) - project.add_user(user, :developer) - end - - it 'does not allow devleoper to removes protected branch', js: true do - visit project_branches_path(project) - - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) - - expect(page).to have_css('.btn-remove.disabled') - end - end end context 'logged in as master' do @@ -136,37 +88,6 @@ describe 'Branches' do expect(page).to have_content("Protected branches can be managed in project settings") end end - - describe 'Delete protected branch' do - before do - visit project_protected_branches_path(project) - set_protected_branch_name('fix') - click_on "Protect" - - within(".protected-branches-list") { expect(page).to have_content('fix') } - expect(ProtectedBranch.count).to eq(1) - end - - it 'removes branch after modal confirmation', js: true do - visit project_branches_path(project) - - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) - - expect(page).to have_content('fix') - expect(find('.all-branches')).to have_selector('li', count: 1) - page.find('[data-target="#modal-delete-branch"]').trigger(:click) - - expect(page).to have_css('.js-delete-branch[disabled]') - fill_in 'delete_branch_input', with: 'fix' - click_link 'Delete protected branch' - - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) - - expect(page).to have_content('No branches to show') - end - end end context 'logged out' do @@ -180,4 +101,13 @@ describe 'Branches' do end end end + + def sorted_branches(repository, count:, sort_by:) + sorted_branches = + repository.branches_sorted_by(sort_by).first(count).map do |branch| + Regexp.escape(branch.name) + end + + Regexp.new(sorted_branches.join('.*')) + end end diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb new file mode 100644 index 00000000000..810f2c39b43 --- /dev/null +++ b/spec/features/projects/clusters_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +feature 'Clusters', :js do + let!(:project) { create(:project, :repository) } + let!(:user) { create(:user) } + + before do + project.add_master(user) + gitlab_sign_in(user) + end + + context 'when user has signed in Google' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:validate_token).and_return(true) + end + + context 'when user does not have a cluster and visits cluster index page' do + before do + visit project_clusters_path(project) + end + + it 'user sees a new page' do + expect(page).to have_button('Create cluster') + end + + context 'when user filled form with valid parameters' do + before do + double.tap do |dbl| + allow(dbl).to receive(:status).and_return('RUNNING') + allow(dbl).to receive(:self_link) + .and_return('projects/gcp-project-12345/zones/us-central1-a/operations/ope-123') + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create).and_return(dbl) + end + + allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) + + fill_in 'cluster_gcp_project_id', with: 'gcp-project-123' + fill_in 'cluster_gcp_cluster_name', with: 'dev-cluster' + click_button 'Create cluster' + end + + it 'user sees a cluster details page and creation status' do + expect(page).to have_content('Cluster is being created on Google Container Engine...') + + Gcp::Cluster.last.make_created! + + expect(page).to have_content('Cluster was successfully created on Google Container Engine') + end + end + + context 'when user filled form with invalid parameters' do + before do + click_button 'Create cluster' + end + + it 'user sees a validation error' do + expect(page).to have_css('#error_explanation') + end + end + end + + context 'when user has a cluster and visits cluster index page' do + let!(:cluster) { create(:gcp_cluster, :created_on_gke, :with_kubernetes_service, project: project) } + + before do + visit project_clusters_path(project) + end + + it 'user sees an cluster details page' do + expect(page).to have_button('Save') + expect(page.find(:css, '.cluster-name').value).to eq(cluster.gcp_cluster_name) + end + + context 'when user disables the cluster' do + before do + page.find(:css, '.js-toggle-cluster').click + click_button 'Save' + end + + it 'user sees the succeccful message' do + expect(page).to have_content('Cluster was successfully updated.') + end + end + + context 'when user destory the cluster' do + before do + page.accept_confirm do + click_link 'Remove integration' + end + end + + it 'user sees creation form with the succeccful message' do + expect(page).to have_content('Cluster integration was successfully removed.') + expect(page).to have_button('Create cluster') + end + end + end + end + + context 'when user has not signed in Google' do + before do + visit project_clusters_path(project) + end + + it 'user sees a login page' do + expect(page).to have_css('.signin-with-google') + end + end +end diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb index 740331fe42a..79e84a4f0a6 100644 --- a/spec/features/projects/commit/builds_spec.rb +++ b/spec/features/projects/commit/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'project commit pipelines', js: true do +feature 'project commit pipelines', :js do given(:project) { create(:project, :repository) } background do @@ -20,7 +20,6 @@ feature 'project commit pipelines', js: true do visit pipelines_project_commit_path(project, project.commit.sha) page.within('.table-holder') do - expect(page).to have_content project.pipelines[0].status # pipeline status expect(page).to have_content project.pipelines[0].id # pipeline ids end end diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 7086f56bb1b..c11a95732b2 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -64,7 +64,7 @@ describe 'Cherry-pick Commits' do end end - context "I cherry-pick a commit from a different branch", js: true do + context "I cherry-pick a commit from a different branch", :js do it do find('.header-action-buttons a.dropdown-toggle').click find(:css, "a[href='#modal-cherry-pick-commit']").click diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb new file mode 100644 index 00000000000..4dbfc6f6edf --- /dev/null +++ b/spec/features/projects/commit/diff_notes_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +feature 'Commit diff', :js do + include RepoHelpers + + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + + before do + project.add_master(user) + sign_in user + end + + %w(inline parallel).each do |view| + context "#{view} view" do + before do + visit project_commit_path(project, sample_commit.id, view: view) + end + + it "adds comment to diff" do + diff_line_num = first('.diff-line-num.new') + + diff_line_num.hover + diff_line_num.find('.js-add-diff-note-button').click + + page.within(first('.diff-viewer')) do + find('.js-note-text').set 'test comment' + + click_button 'Comment' + + expect(page).to have_content('test comment') + end + end + end + end +end diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb index 82d73fe8531..87ffc2a0b90 100644 --- a/spec/features/projects/compare_spec.rb +++ b/spec/features/projects/compare_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe "Compare", js: true do +describe "Compare", :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb index 2d1a9b931b5..e445758cb5e 100644 --- a/spec/features/projects/deploy_keys_spec.rb +++ b/spec/features/projects/deploy_keys_spec.rb @@ -20,7 +20,7 @@ describe 'Project deploy keys', :js do page.within(find('.deploy-keys')) do expect(page).to have_selector('.deploy-keys li', count: 1) - click_on 'Remove' + accept_confirm { find(:button, text: 'Remove').send_keys(:return) } expect(page).not_to have_selector('.fa-spinner', count: 0) expect(page).to have_selector('.deploy-keys li', count: 0) diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb index fe8567ce348..36809240f76 100644 --- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -17,7 +17,7 @@ feature 'Developer views empty project instructions' do expect_instructions_for('http') end - scenario 'switches to SSH', js: true do + scenario 'switches to SSH', :js do visit_project select_protocol('SSH') @@ -37,7 +37,7 @@ feature 'Developer views empty project instructions' do expect_instructions_for('ssh') end - scenario 'switches to HTTP', js: true do + scenario 'switches to HTTP', :js do visit_project select_protocol('HTTP') diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb index 17f914c9c17..7a372757523 100644 --- a/spec/features/projects/edit_spec.rb +++ b/spec/features/projects/edit_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Project edit', js: true do +feature 'Project edit', :js do let(:admin) { create(:admin) } let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 56addd64056..5fc3ba54f65 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -193,12 +193,14 @@ feature 'Environment' do create(:environment, project: project, name: 'staging-1.0/review', state: :available) - - visit folder_project_environments_path(project, id: 'staging-1.0') end it 'renders a correct environment folder' do - expect(page).to have_http_status(:ok) + reqs = inspect_requests do + visit folder_project_environments_path(project, id: 'staging-1.0') + end + + expect(reqs.first.status_code).to eq(200) expect(page).to have_content('Environments / staging-1.0') end end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index af7ad365546..b4eb5795470 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -145,13 +145,13 @@ feature 'Environments page', :js do expect(page).to have_content(action.name.humanize) end - it 'allows to play a manual action', js: true do + it 'allows to play a manual action', :js do expect(action).to be_manual find('.js-dropdown-play-icon-container').click expect(page).to have_content(action.name.humanize) - expect { find('.js-manual-action-link').trigger('click') } + expect { find('.js-manual-action-link').click } .not_to change { Ci::Pipeline.count } end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 57722276d79..951456763dc 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -6,7 +6,7 @@ describe 'Edit Project Settings' do let!(:issue) { create(:issue, project: project) } let(:non_member) { create(:user) } - describe 'project features visibility selectors', js: true do + describe 'project features visibility selectors', :js do before do project.team << [member, :master] sign_in(member) @@ -22,7 +22,7 @@ describe 'Edit Project Settings' do # disable by clicking toggle toggle_feature_off("project[project_feature_attributes][#{tool_name}_access_level]") page.within('.sharing-permissions') do - click_button 'Save changes' + find('input[value="Save changes"]').click end wait_for_requests expect(page).not_to have_selector(".shortcuts-#{shortcut_name}") @@ -30,7 +30,7 @@ describe 'Edit Project Settings' do # re-enable by clicking toggle again toggle_feature_on("project[project_feature_attributes][#{tool_name}_access_level]") page.within('.sharing-permissions') do - click_button 'Save changes' + find('input[value="Save changes"]').click end wait_for_requests expect(page).to have_selector(".shortcuts-#{shortcut_name}") @@ -163,7 +163,7 @@ describe 'Edit Project Settings' do end end - describe 'repository visibility', js: true do + describe 'repository visibility', :js do before do project.team << [member, :master] sign_in(member) diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb index f62a9edd37e..84197e45dcb 100644 --- a/spec/features/projects/files/browse_files_spec.rb +++ b/spec/features/projects/files/browse_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'user browses project', js: true do +feature 'user browses project', :js do let(:project) { create(:project, :repository) } let(:user) { create(:user) } diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb index cebb238dda1..3c3a5326538 100644 --- a/spec/features/projects/files/dockerfile_dropdown_spec.rb +++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb @@ -16,7 +16,7 @@ feature 'User wants to add a Dockerfile file' do expect(page).to have_css('.dockerfile-selector') end - scenario 'user can pick a Dockerfile file from the dropdown', js: true do + scenario 'user can pick a Dockerfile file from the dropdown', :js do find('.js-dockerfile-selector').click wait_for_requests diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb index c7e3f657639..3ab43b3c656 100644 --- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb +++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb @@ -1,24 +1,24 @@ require 'spec_helper' -feature 'User uses soft wrap whilst editing file', js: true do +feature 'User uses soft wrap whilst editing file', :js do before do user = create(:user) project = create(:project, :repository) project.team << [user, :master] sign_in user visit project_new_blob_path(project, 'master', file_name: 'test_file-name') - editor = find('.file-editor.code') - editor.click - editor.send_keys 'Touch water with paw then recoil in horror chase dog then - run away chase the pig around the house eat owner\'s food, and knock - dish off table head butt cant eat out of my own dish. Cat is love, cat - is life rub face on everything poop on grasses so meow. Playing with - balls of wool flee in terror at cucumber discovered on floor run in - circles tuxedo cats always looking dapper, but attack dog, run away - and pretend to be victim so all of a sudden cat goes crazy, yet chase - laser. Make muffins sit in window and stare ooo, a bird! yum lick yarn - hanging out of own butt jump off balcony, onto stranger\'s head yet - chase laser. Purr for no reason stare at ceiling hola te quiero.'.squish + page.within('.file-editor.code') do + find('.ace_text-input', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then + run away chase the pig around the house eat owner\'s food, and knock + dish off table head butt cant eat out of my own dish. Cat is love, cat + is life rub face on everything poop on grasses so meow. Playing with + balls of wool flee in terror at cucumber discovered on floor run in + circles tuxedo cats always looking dapper, but attack dog, run away + and pretend to be victim so all of a sudden cat goes crazy, yet chase + laser. Make muffins sit in window and stare ooo, a bird! yum lick yarn + hanging out of own butt jump off balcony, onto stranger\'s head yet + chase laser. Purr for no reason stare at ceiling hola te quiero.'.squish + end end let(:toggle_button) { find('.soft-wrap-toggle') } @@ -36,6 +36,6 @@ feature 'User uses soft wrap whilst editing file', js: true do end def get_content_width - find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/) + find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/).to_i end end diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb index 7f97fdb8cc9..618725ee781 100644 --- a/spec/features/projects/files/find_file_keyboard_spec.rb +++ b/spec/features/projects/files/find_file_keyboard_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Find file keyboard shortcuts', js: true do +feature 'Find file keyboard shortcuts', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb index e2044c9d5aa..81d68c3d67c 100644 --- a/spec/features/projects/files/gitignore_dropdown_spec.rb +++ b/spec/features/projects/files/gitignore_dropdown_spec.rb @@ -13,7 +13,7 @@ feature 'User wants to add a .gitignore file' do expect(page).to have_css('.gitignore-selector') end - scenario 'user can pick a .gitignore file from the dropdown', js: true do + scenario 'user can pick a .gitignore file from the dropdown', :js do find('.js-gitignore-selector').click wait_for_requests within '.gitignore-selector' do diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb index ab242b0b0b5..8e58fa7bd56 100644 --- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -13,7 +13,7 @@ feature 'User wants to add a .gitlab-ci.yml file' do expect(page).to have_css('.gitlab-ci-yml-selector') end - scenario 'user can pick a template from the dropdown', js: true do + scenario 'user can pick a template from the dropdown', :js do find('.js-gitlab-ci-yml-selector').click wait_for_requests within '.gitlab-ci-yml-selector' do diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb index 95af263bcac..6c5b1086ec1 100644 --- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb +++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'project owner creates a license file', js: true do +feature 'project owner creates a license file', :js do let(:project_master) { create(:user) } let(:project) { create(:project, :repository) } background do diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb index 7bcab01c739..6c616bf0456 100644 --- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb +++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'project owner sees a link to create a license file in empty project', js: true do +feature 'project owner sees a link to create a license file in empty project', :js do let(:project_master) { create(:user) } let(:project) { create(:project) } background do diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb index 48003eeaa87..f95a60e5194 100644 --- a/spec/features/projects/files/template_type_dropdown_spec.rb +++ b/spec/features/projects/files/template_type_dropdown_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Template type dropdown selector', js: true do +feature 'Template type dropdown selector', :js do let(:project) { create(:project, :repository) } let(:user) { create(:user) } diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb index 9bcd5beabb8..64fe350f3dc 100644 --- a/spec/features/projects/files/undo_template_spec.rb +++ b/spec/features/projects/files/undo_template_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Template Undo Button', js: true do +feature 'Template Undo Button', :js do let(:project) { create(:project, :repository) } let(:user) { create(:user) } diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb new file mode 100644 index 00000000000..e10d29e5eea --- /dev/null +++ b/spec/features/projects/fork_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe 'Project fork' do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + + before do + sign_in user + end + + it 'allows user to fork project' do + visit project_path(project) + + expect(page).not_to have_css('a.disabled', text: 'Fork') + end + + it 'disables fork button when user has exceeded project limit' do + user.projects_limit = 0 + user.save! + + visit project_path(project) + + expect(page).to have_css('a.disabled', text: 'Fork') + end + + context 'master in group' do + before do + group = create(:group) + group.add_master(user) + end + + it 'allows user to fork project to group or to user namespace' do + visit project_path(project) + + expect(page).not_to have_css('a.disabled', text: 'Fork') + + click_link 'Fork' + + expect(page).to have_css('.fork-thumbnail', count: 2) + expect(page).not_to have_css('.fork-thumbnail.disabled') + end + + it 'allows user to fork project to group and not user when exceeded project limit' do + user.projects_limit = 0 + user.save! + + visit project_path(project) + + expect(page).not_to have_css('a.disabled', text: 'Fork') + + click_link 'Fork' + + expect(page).to have_css('.fork-thumbnail', count: 2) + expect(page).to have_css('.fork-thumbnail.disabled') + end + end +end diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb index cff3b1f5743..1c988726ae6 100644 --- a/spec/features/projects/gfm_autocomplete_load_spec.rb +++ b/spec/features/projects/gfm_autocomplete_load_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'GFM autocomplete loading', js: true do +describe 'GFM autocomplete loading', :js do let(:project) { create(:project) } before do diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 62d244ff259..461aa39d0ad 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' # It looks up for any sensitive word inside the JSON, so if a sensitive word is found # we''l have to either include it adding the model that includes it to the +safe_list+ # or make sure the attribute is blacklisted in the +import_export.yml+ configuration -feature 'Import/Export - project export integration test', js: true do +feature 'Import/Export - project export integration test', :js do include Select2Helper include ExportFileHelper @@ -41,7 +41,7 @@ feature 'Import/Export - project export integration test', js: true do expect(page).to have_content('Export project') - click_link 'Export project' + find(:link, 'Export project').send_keys(:return) visit edit_project_path(project) diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index e5c7781a096..af125e1b9d3 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Import/Export - project import integration test', js: true do +feature 'Import/Export - project import integration test', :js do include Select2Helper let(:user) { create(:user) } @@ -27,6 +27,7 @@ feature 'Import/Export - project import integration test', js: true do select2(namespace.id, from: '#project_namespace_id') fill_in :project_path, with: project_path, visible: true + click_import_project_tab click_link 'GitLab export' expect(page).to have_content('Import an exported GitLab project') @@ -51,6 +52,7 @@ feature 'Import/Export - project import integration test', js: true do context 'path is not prefilled' do scenario 'user imports an exported project successfully' do visit new_project_path + click_import_project_tab click_link 'GitLab export' fill_in :path, with: 'test-project-path', visible: true @@ -72,6 +74,7 @@ feature 'Import/Export - project import integration test', js: true do select2(user.namespace.id, from: '#project_namespace_id') fill_in :project_path, with: project.name, visible: true + click_import_project_tab click_link 'GitLab export' attach_file('file', file) click_on 'Import project' @@ -81,19 +84,6 @@ feature 'Import/Export - project import integration test', js: true do end end - context 'when limited to the default user namespace' do - scenario 'passes correct namespace ID in the URL' do - visit new_project_path - - fill_in :project_path, with: 'test-project-path', visible: true - - click_link 'GitLab export' - - expect(page).to have_content('GitLab project export') - expect(URI.parse(current_url).query).to eq("namespace_id=#{user.namespace.id}&path=test-project-path") - end - end - def wiki_exists?(project) wiki = ProjectWiki.new(project) File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty? @@ -102,4 +92,8 @@ feature 'Import/Export - project import integration test', js: true do def project_hook_exists?(project) Gitlab::Git::Hook.new('post-receive', project.repository.raw_repository).exists? end + + def click_import_project_tab + find('#import-project-tab').click + end end diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb index 691b0e1e4ca..e76bc6f1220 100644 --- a/spec/features/projects/import_export/namespace_export_file_spec.rb +++ b/spec/features/projects/import_export/namespace_export_file_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Import/Export - Namespace export file cleanup', js: true do +feature 'Import/Export - Namespace export file cleanup', :js do let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } @@ -52,7 +52,7 @@ feature 'Import/Export - Namespace export file cleanup', js: true do expect(page).to have_content('Export project') - click_link 'Export project' + find(:link, 'Export project').send_keys(:return) visit edit_project_path(project) diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index d2789d0aa52..a012db8fd27 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' -feature 'issuable templates', js: true do +feature 'issuable templates', :js do + include ProjectForksHelper + let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } + let(:issue_form_location) { '#content-body .issuable-details .detail-page-description' } before do project.team << [user, :master] @@ -28,14 +31,17 @@ feature 'issuable templates', js: true do longtemplate_content, message: 'added issue template', branch_name: 'master') - visit edit_project_issue_path project, issue - fill_in :'issue[title]', with: 'test issue title' + visit project_issue_path project, issue + page.within('.content .issuable-actions') do + click_on 'Edit' + end + fill_in :'issuable-title', with: 'test issue title' end scenario 'user selects "bug" template' do select_template 'bug' wait_for_requests - assert_template + assert_template(page_part: issue_form_location) save_changes end @@ -43,30 +49,19 @@ feature 'issuable templates', js: true do select_template 'bug' wait_for_requests select_option 'No template' - assert_template('') + assert_template(expected_content: '', page_part: issue_form_location) save_changes('') end scenario 'user selects "bug" template, edits description and then selects "reset template"' do select_template 'bug' wait_for_requests - find_field('issue_description').send_keys(description_addition) - assert_template(template_content + description_addition) + find_field('issue-description').send_keys(description_addition) + assert_template(expected_content: template_content + description_addition, page_part: issue_form_location) select_option 'Reset template' - assert_template + assert_template(page_part: issue_form_location) save_changes end - - it 'updates height of markdown textarea' do - start_height = page.evaluate_script('$(".markdown-area").outerHeight()') - - select_template 'test' - wait_for_requests - - end_height = page.evaluate_script('$(".markdown-area").outerHeight()') - - expect(end_height).not_to eq(start_height) - end end context 'user creates an issue using templates, with a prior description' do @@ -81,15 +76,18 @@ feature 'issuable templates', js: true do template_content, message: 'added issue template', branch_name: 'master') - visit edit_project_issue_path project, issue - fill_in :'issue[title]', with: 'test issue title' - fill_in :'issue[description]', with: prior_description + visit project_issue_path project, issue + page.within('.content .issuable-actions') do + click_on 'Edit' + end + fill_in :'issuable-title', with: 'test issue title' + fill_in :'issue-description', with: prior_description end scenario 'user selects "bug" template' do select_template 'bug' wait_for_requests - assert_template("#{template_content}") + assert_template(page_part: issue_form_location) save_changes end end @@ -120,15 +118,13 @@ feature 'issuable templates', js: true do context 'user creates a merge request from a forked project using templates' do let(:template_content) { 'this is a test "feature-proposal" template' } let(:fork_user) { create(:user) } - let(:fork_project) { create(:project, :public, :repository) } - let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) } + let(:forked_project) { fork_project(project, fork_user, repository: true) } + let(:merge_request) { create(:merge_request, :with_diffs, source_project: forked_project, target_project: project) } background do sign_out(:user) project.team << [fork_user, :developer] - fork_project.team << [fork_user, :master] - create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) sign_in(fork_user) @@ -154,8 +150,10 @@ feature 'issuable templates', js: true do end end - def assert_template(expected_content = template_content) - expect(find('textarea')['value']).to eq(expected_content) + def assert_template(expected_content: template_content, page_part: '#content-body') + page.within(page_part) do + expect(find('textarea')['value']).to eq(expected_content) + end end def save_changes(expected_content = template_content) diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb deleted file mode 100644 index 9fc03f49f5b..00000000000 --- a/spec/features/projects/issues/list_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -feature 'Issues List' do - let(:user) { create(:user) } - let(:project) { create(:project) } - - background do - project.team << [user, :developer] - - sign_in(user) - end - - scenario 'user does not see create new list button' do - create(:issue, project: project) - - visit project_issues_path(project) - - expect(page).not_to have_selector('.js-new-board-list') - end -end diff --git a/spec/features/projects/issues/user_views_issues_spec.rb b/spec/features/projects/issues/user_views_issues_spec.rb new file mode 100644 index 00000000000..d35009b8974 --- /dev/null +++ b/spec/features/projects/issues/user_views_issues_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe 'User views issues' do + set(:user) { create(:user) } + + shared_examples_for 'shows issues' do + it 'shows issues' do + expect(page).to have_content(project.name) + .and have_content(issue1.title) + .and have_content(issue2.title) + .and have_no_selector('.js-new-board-list') + end + end + + context 'when project is public' do + set(:project) { create(:project_empty_repo, :public) } + set(:issue1) { create(:issue, project: project) } + set(:issue2) { create(:issue, project: project) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + + context 'when not signed in' do + before do + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + end + + context 'when project is internal' do + set(:project) { create(:project_empty_repo, :internal) } + set(:issue1) { create(:issue, project: project) } + set(:issue2) { create(:issue, project: project) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_issues_path(project)) + end + + include_examples 'shows issues' + end + end +end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 21c9acc7ac0..5d9208ebadd 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -21,12 +21,12 @@ describe 'User browses a job', :js do expect(page).to have_content("Job ##{build.id}") expect(page).to have_css('#build-trace') - click_link('Erase') + accept_confirm { click_link('Erase') } + expect(page).to have_no_css('.artifacts') expect(build).not_to have_trace expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_metadata.exists?).to be_falsy - expect(page).to have_no_css('.artifacts') page.within('.erased') do expect(page).to have_content('Job has been erased') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index a4ed589f3de..c2a0d2395a9 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -299,14 +299,14 @@ feature 'Jobs' do end shared_examples 'expected variables behavior' do - it 'shows variable key and value after click', js: true do - expect(page).to have_css('.reveal-variables') + it 'shows variable key and value after click', :js do + expect(page).to have_css('.js-reveal-variables') expect(page).not_to have_css('.js-build-variable') expect(page).not_to have_css('.js-build-value') click_button 'Reveal Variables' - expect(page).not_to have_css('.reveal-variables') + expect(page).not_to have_css('.js-reveal-variables') expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') end @@ -380,7 +380,6 @@ feature 'Jobs' do end it 'loads the page and shows all needed controls' do - expect(page.status_code).to eq(200) expect(page).to have_content 'Retry' end end @@ -392,11 +391,10 @@ feature 'Jobs' do job.run! visit project_job_path(project, job) find('.js-cancel-job').click() - find('.js-retry-button').trigger('click') + find('.js-retry-button').click end it 'shows the right status and buttons', :js do - expect(page).to have_http_status(200) page.within('aside.right-sidebar') do expect(page).to have_content 'Cancel' end @@ -443,28 +441,30 @@ feature 'Jobs' do context 'access source' do context 'job from project' do before do - Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } job.run! - visit project_job_path(project, job) - find('.js-raw-link-controller').click() end it 'sends the right headers' do - expect(page.status_code).to eq(200) - expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path)) + requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do + visit raw_project_job_path(project, job) + end + + expect(requests.first.status_code).to eq(200) + expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path)) end end context 'job from other project' do before do - Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } job2.run! - visit raw_project_job_path(project, job2) end it 'sends the right headers' do - expect(page.status_code).to eq(404) + requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do + visit raw_project_job_path(project, job2) + end + expect(requests.first.status_code).to eq(404) end end end @@ -473,8 +473,6 @@ feature 'Jobs' do let(:existing_file) { Tempfile.new('existing-trace-file').path } before do - Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } - job.run! end @@ -483,16 +481,14 @@ feature 'Jobs' do allow_any_instance_of(Gitlab::Ci::Trace) .to receive(:paths) .and_return([existing_file]) - - visit project_job_path(project, job) - - find('.js-raw-link-controller').click end it 'sends the right headers' do - expect(page.status_code).to eq(200) - expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(existing_file) + requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do + visit raw_project_job_path(project, job) + end + expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(requests.first.response_headers['X-Sendfile']).to eq(existing_file) end end diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb index 5716d151250..e8c70dec854 100644 --- a/spec/features/projects/labels/subscription_spec.rb +++ b/spec/features/projects/labels/subscription_spec.rb @@ -13,7 +13,7 @@ feature 'Labels subscription' do sign_in user end - scenario 'users can subscribe/unsubscribe to labels', js: true do + scenario 'users can subscribe/unsubscribe to labels', :js do visit project_labels_path(project) expect(page).to have_content('bug') diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 8f85e972027..d063f5c27b5 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -17,7 +17,7 @@ feature 'Prioritize labels' do sign_in user end - scenario 'user can prioritize a group label', js: true do + scenario 'user can prioritize a group label', :js do visit project_labels_path(project) expect(page).to have_content('Star labels to start sorting by priority') @@ -34,7 +34,7 @@ feature 'Prioritize labels' do end end - scenario 'user can unprioritize a group label', js: true do + scenario 'user can unprioritize a group label', :js do create(:label_priority, project: project, label: feature, priority: 1) visit project_labels_path(project) @@ -52,7 +52,7 @@ feature 'Prioritize labels' do end end - scenario 'user can prioritize a project label', js: true do + scenario 'user can prioritize a project label', :js do visit project_labels_path(project) expect(page).to have_content('Star labels to start sorting by priority') @@ -69,7 +69,7 @@ feature 'Prioritize labels' do end end - scenario 'user can unprioritize a project label', js: true do + scenario 'user can unprioritize a project label', :js do create(:label_priority, project: project, label: bug, priority: 1) visit project_labels_path(project) @@ -88,7 +88,7 @@ feature 'Prioritize labels' do end end - scenario 'user can sort prioritized labels and persist across reloads', js: true do + scenario 'user can sort prioritized labels and persist across reloads', :js do create(:label_priority, project: project, label: bug, priority: 1) create(:label_priority, project: project, label: feature, priority: 2) diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb index c8988aa63a7..6d729f2f85f 100644 --- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb +++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Members > Group requester cannot request access to project', js: true do +feature 'Projects > Members > Group requester cannot request access to project', :js do let(:user) { create(:user) } let(:owner) { create(:user) } let(:group) { create(:group, :public, :access_requestable) } diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 9950272af08..7f067aadec6 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Members > Groups with access list', js: true do +feature 'Projects > Members > Groups with access list', :js do let(:user) { create(:user) } let(:group) { create(:group, :public) } let(:project) { create(:project, :public) } @@ -31,6 +31,7 @@ feature 'Projects > Members > Groups with access list', js: true do tomorrow = Date.today + 3 fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") + find('body').click wait_for_requests page.within(find('li.group_member')) do @@ -40,7 +41,7 @@ feature 'Projects > Members > Groups with access list', js: true do scenario 'deletes group link' do page.within(first('.group_member')) do - find('.btn-remove').click + accept_confirm { find('.btn-remove').click } end wait_for_requests diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index cd621b6b3ce..0f88f4cb1e8 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Members > Master adds member with expiration date', js: true do +feature 'Projects > Members > Master adds member with expiration date', :js do include Select2Helper include ActiveSupport::Testing::TimeHelpers @@ -20,7 +20,7 @@ feature 'Projects > Members > Master adds member with expiration date', js: true page.within '.users-project-form' do select2(new_member.id, from: '#user_ids', multiple: true) - fill_in 'expires_at', with: date.to_s(:medium) + fill_in 'expires_at', with: date.to_s(:medium) + "\n" click_on 'Add to project' end @@ -37,7 +37,7 @@ feature 'Projects > Members > Master adds member with expiration date', js: true visit project_project_members_path(project) page.within "#project_member_#{new_member.project_members.first.id}" do - find('.js-access-expiration-date').set date.to_s(:medium) + find('.js-access-expiration-date').set date.to_s(:medium) + "\n" wait_for_requests expect(page).to have_content('Expires in 3 days') end diff --git a/spec/features/projects/members/share_with_group_spec.rb b/spec/features/projects/members/share_with_group_spec.rb index 3b368f8e25d..3198798306c 100644 --- a/spec/features/projects/members/share_with_group_spec.rb +++ b/spec/features/projects/members/share_with_group_spec.rb @@ -41,7 +41,7 @@ feature 'Project > Members > Share with Group', :js do select2 group_to_share_with.id, from: '#link_group_id' page.find('body').click - find('.btn-create').trigger('click') + find('.btn-create').click page.within('.project-members-groups') do expect(page).to have_content(group_to_share_with.name) @@ -123,7 +123,7 @@ feature 'Project > Members > Share with Group', :js do fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d') page.find('body').click - find('.btn-create').trigger('click') + find('.btn-create').click end scenario 'the group link shows the expiration time with a warning class' do @@ -149,7 +149,7 @@ feature 'Project > Members > Share with Group', :js do create(:group).add_owner(master) visit project_settings_members_path(project) - execute_script 'GroupsSelect.PER_PAGE = 1;' + execute_script 'GROUP_SELECT_PER_PAGE = 1;' open_select2 '#link_group_id' end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index 0fbe1ddb2a5..4eb36156812 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -60,7 +60,7 @@ feature 'Projects > Members > User requests access', :js do expect(project.requesters.exists?(user_id: user)).to be_truthy - click_link 'Withdraw Access Request' + accept_confirm { click_link 'Withdraw Access Request' } expect(project.requesters.exists?(user_id: user)).to be_falsey expect(page).to have_content 'Your access request to the project has been withdrawn.' diff --git a/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb b/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb index 6c0b5e279d5..c35ba2d7016 100644 --- a/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb +++ b/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb @@ -62,4 +62,23 @@ describe 'User accepts a merge request', :js do wait_for_requests end end + + context 'when modifying the merge commit message' do + before do + merge_request.mark_as_mergeable + + visit(merge_request_path(merge_request)) + end + + it 'accepts a merge request' do + click_button('Modify commit message') + fill_in('Commit message', with: 'wow such merge') + + click_button('Merge') + + page.within('.status-box') do + expect(page).to have_content('Merged') + end + end + end end diff --git a/spec/features/projects/merge_requests/user_closes_merge_request_spec.rb b/spec/features/projects/merge_requests/user_closes_merge_request_spec.rb new file mode 100644 index 00000000000..b257f447439 --- /dev/null +++ b/spec/features/projects/merge_requests/user_closes_merge_request_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'User closes a merge requests', :js do + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(merge_request_path(merge_request)) + end + + it 'closes a merge request' do + click_link('Close merge request', match: :first) + + expect(page).to have_content(merge_request.title) + expect(page).to have_content('Closed by') + end +end diff --git a/spec/features/projects/merge_requests/user_comments_on_commit_spec.rb b/spec/features/projects/merge_requests/user_comments_on_commit_spec.rb new file mode 100644 index 00000000000..0a952cfc2a9 --- /dev/null +++ b/spec/features/projects/merge_requests/user_comments_on_commit_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'User comments on a commit', :js do + include MergeRequestDiffHelpers + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_commit_path(project, sample_commit.id)) + end + + include_examples 'comment on merge request file' +end diff --git a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb new file mode 100644 index 00000000000..e3f90a78cb5 --- /dev/null +++ b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb @@ -0,0 +1,173 @@ +require 'spec_helper' + +describe 'User comments on a diff', :js do + include MergeRequestDiffHelpers + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') + end + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(diffs_project_merge_request_path(project, merge_request)) + end + + context 'when viewing comments' do + context 'when toggling inline comments' do + context 'in a single file' do + it 'hides a comment' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: 'Line is wrong') + click_button('Comment') + end + + page.within('.files > div:nth-child(3)') do + expect(page).to have_content('Line is wrong') + + find('.js-toggle-diff-comments').click + + expect(page).not_to have_content('Line is wrong') + end + end + end + + context 'in multiple files' do + it 'toggles comments' do + click_diff_line(find("[id='#{sample_compare.changes[0][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: 'Line is correct') + click_button('Comment') + end + + wait_for_requests + + page.within('.files > div:nth-child(2) .note-body > .note-text') do + expect(page).to have_content('Line is correct') + end + + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: 'Line is wrong') + click_button('Comment') + end + + wait_for_requests + + # Hide the comment. + page.within('.files > div:nth-child(3)') do + find('.js-toggle-diff-comments').click + + expect(page).not_to have_content('Line is wrong') + end + + # At this moment a user should see only one comment. + # The other one should be hidden. + page.within('.files > div:nth-child(2) .note-body > .note-text') do + expect(page).to have_content('Line is correct') + end + + # Show the comment. + page.within('.files > div:nth-child(3)') do + find('.js-toggle-diff-comments').click + end + + # Now both the comments should be shown. + page.within('.files > div:nth-child(3) .note-body > .note-text') do + expect(page).to have_content('Line is wrong') + end + + page.within('.files > div:nth-child(2) .note-body > .note-text') do + expect(page).to have_content('Line is correct') + end + + # Check the same comments in the side-by-side view. + execute_script("window.scrollTo(0,0);") + click_link('Side-by-side') + + wait_for_requests + + page.within('.files > div:nth-child(3) .parallel .note-body > .note-text') do + expect(page).to have_content('Line is wrong') + end + + page.within('.files > div:nth-child(2) .parallel .note-body > .note-text') do + expect(page).to have_content('Line is correct') + end + end + end + end + end + + context 'when adding comments' do + include_examples 'comment on merge request file' + end + + context 'when editing comments' do + it 'edits a comment' do + click_diff_line(find("[id='#{sample_commit.line_code}']")) + + page.within('.js-discussion-note-form') do + fill_in(:note_note, with: 'Line is wrong') + click_button('Comment') + end + + page.within('.diff-file:nth-of-type(5) .note') do + find('.js-note-edit').click + + page.within('.current-note-edit-form') do + fill_in('note_note', with: 'Typo, please fix') + click_button('Save comment') + end + + expect(page).not_to have_button('Save comment', disabled: true) + end + + page.within('.diff-file:nth-of-type(5) .note') do + expect(page).to have_content('Typo, please fix').and have_no_content('Line is wrong') + end + end + end + + context 'when deleting comments' do + it 'deletes a comment' do + click_diff_line(find("[id='#{sample_commit.line_code}']")) + + page.within('.js-discussion-note-form') do + fill_in(:note_note, with: 'Line is wrong') + click_button('Comment') + end + + page.within('.notes-tab .badge') do + expect(page).to have_content('1') + end + + page.within('.diff-file:nth-of-type(5) .note') do + find('.more-actions').click + find('.more-actions .dropdown-menu li', match: :first) + + accept_confirm { find('.js-note-delete').click } + end + + page.within('.merge-request-tabs') do + find('.notes-tab').click + end + + wait_for_requests + + expect(page).not_to have_css('.notes .discussion') + + page.within('.notes-tab .badge') do + expect(page).to have_content('0') + end + end + end +end diff --git a/spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb b/spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb new file mode 100644 index 00000000000..2eb652147ce --- /dev/null +++ b/spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe 'User comments on a merge request', :js do + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(merge_request_path(merge_request)) + end + + it 'adds a comment' do + page.within('.js-main-target-form') do + fill_in(:note_note, with: '# Comment with a header') + click_button('Comment') + end + + wait_for_requests + + page.within('.note') do + expect(page).to have_content('Comment with a header') + expect(page).not_to have_css('#comment-with-a-header') + end + end + + it 'loads new comment' do + # Add new comment in background in order to check + # if it's going to be loaded automatically for current user. + create(:diff_note_on_merge_request, project: project, noteable: merge_request, author: user, note: 'Line is wrong') + + # Trigger a refresh of notes. + execute_script("$(document).trigger('visibilitychange');") + wait_for_requests + + page.within('.notes .discussion') do + expect(page).to have_content("#{user.name} #{user.to_reference} started a discussion") + expect(page).to have_content(sample_commit.line_code_path) + expect(page).to have_content('Line is wrong') + end + + page.within('.notes-tab .badge') do + expect(page).to have_content('1') + end + end +end diff --git a/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb b/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb new file mode 100644 index 00000000000..f285c6c8783 --- /dev/null +++ b/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'User creates a merge request', :js do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_new_merge_request_path(project)) + end + + it 'creates a merge request' do + find('.js-source-branch').click + click_link('fix') + + find('.js-target-branch').click + click_link('feature') + + click_button('Compare branches') + + fill_in('merge_request_title', with: 'Wiki Feature') + click_button('Submit merge request') + + page.within('.merge-request') do + expect(page).to have_content('Wiki Feature') + end + + wait_for_requests + end +end diff --git a/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb b/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb new file mode 100644 index 00000000000..3d19a2923b9 --- /dev/null +++ b/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'User edits a merge request', :js do + include Select2Helper + + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(edit_project_merge_request_path(project, merge_request)) + end + + it 'changes the target branch' do + expect(page).to have_content('Target branch') + + select2('merge-test', from: '#merge_request_target_branch') + click_button('Save changes') + + expect(page).to have_content("Request to merge #{merge_request.source_branch} into merge-test") + expect(page).to have_content("changed target branch from #{merge_request.target_branch} to merge-test") + end +end diff --git a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb new file mode 100644 index 00000000000..4ca435491cb --- /dev/null +++ b/spec/features/projects/merge_requests/user_manages_subscription_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe 'User manages subscription', :js do + let(:project) { create(:project, :public, :repository) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(merge_request_path(merge_request)) + end + + it 'toggles subscription' do + subscribe_button = find('.js-issuable-subscribe-button') + + expect(subscribe_button).to have_content('Subscribe') + + click_on('Subscribe') + + wait_for_requests + + expect(subscribe_button).to have_content('Unsubscribe') + + click_on('Unsubscribe') + + wait_for_requests + + expect(subscribe_button).to have_content('Subscribe') + end +end diff --git a/spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb b/spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb new file mode 100644 index 00000000000..ba3c9789da1 --- /dev/null +++ b/spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'User reopens a merge requests', :js do + let(:project) { create(:project, :public, :repository) } + let!(:merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(merge_request_path(merge_request)) + end + + it 'reopens a merge request' do + click_link('Reopen merge request', match: :first) + + page.within('.status-box') do + expect(page).to have_content('Open') + end + end +end diff --git a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb new file mode 100644 index 00000000000..d8d9f7e2a8c --- /dev/null +++ b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe 'User sorts merge requests' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let!(:merge_request2) do + create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') + end + let(:project) { create(:project, :public, :repository) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_merge_requests_path(project)) + end + + it 'keeps the sort option' do + find('button.dropdown-toggle').click + + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link('Last updated') + end + + visit(merge_requests_dashboard_path(assignee_id: user.id)) + + expect(find('.issues-filters')).to have_content('Last updated') + + visit(project_merge_requests_path(project)) + + expect(find('.issues-filters')).to have_content('Last updated') + end + + context 'when merge requests have awards' do + before do + create_list(:award_emoji, 2, awardable: merge_request) + create(:award_emoji, :downvote, awardable: merge_request) + + create(:award_emoji, awardable: merge_request2) + create_list(:award_emoji, 2, :downvote, awardable: merge_request2) + end + + it 'sorts by popularity' do + find('button.dropdown-toggle').click + + page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + click_link('Popularity') + end + + page.within('.mr-list') do + page.within('li.merge-request:nth-child(1)') do + expect(page).to have_content(merge_request.title) + expect(page).to have_content('2 1') + end + + page.within('li.merge-request:nth-child(2)') do + expect(page).to have_content(merge_request2.title) + expect(page).to have_content('1 2') + end + end + end + end +end diff --git a/spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb new file mode 100644 index 00000000000..6c695bd7aa9 --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'User views all merge requests' do + let!(:closed_merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:project) { create(:project, :public) } + + before do + visit(project_merge_requests_path(project, state: :all)) + end + + it 'shows all merge requests' do + expect(page).to have_content(merge_request.title).and have_content(closed_merge_request.title) + end +end diff --git a/spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb new file mode 100644 index 00000000000..853809fe87a --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'User views closed merge requests' do + let!(:closed_merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:project) { create(:project, :public) } + + before do + visit(project_merge_requests_path(project, state: :closed)) + end + + it 'shows closed merge requests' do + expect(page).to have_content(closed_merge_request.title).and have_no_content(merge_request.title) + end +end diff --git a/spec/features/projects/merge_requests/user_views_diffs_spec.rb b/spec/features/projects/merge_requests/user_views_diffs_spec.rb new file mode 100644 index 00000000000..295eb02b625 --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_diffs_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe 'User views diffs', :js do + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') + end + let(:project) { create(:project, :public, :repository) } + + before do + visit(diffs_project_merge_request_path(project, merge_request)) + + wait_for_requests + end + + shared_examples 'unfold diffs' do + it 'unfolds diffs' do + first('.js-unfold').click + + expect(first('.text-file')).to have_content('.bundle') + end + end + + it 'shows diffs' do + expect(page).to have_css('.tab-content #diffs.active') + expect(page).to have_css('#parallel-diff-btn', count: 1) + expect(page).to have_css('#inline-diff-btn', count: 1) + end + + context 'when in the inline view' do + include_examples 'unfold diffs' + end + + context 'when in the side-by-side view' do + before do + click_link('Side-by-side') + + wait_for_requests + end + + it 'shows diffs in parallel' do + expect(page).to have_css('.parallel') + end + + include_examples 'unfold diffs' + end +end diff --git a/spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb new file mode 100644 index 00000000000..eb012694f1e --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'User views merged merge requests' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let!(:merged_merge_request) { create(:merged_merge_request, source_project: project, target_project: project) } + let(:project) { create(:project, :public) } + + before do + visit(project_merge_requests_path(project, state: :merged)) + end + + it 'shows merged merge requests' do + expect(page).to have_content(merged_merge_request.title).and have_no_content(merge_request.title) + end +end diff --git a/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb new file mode 100644 index 00000000000..3aac93eaf7c --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' + +describe 'User views an open merge request' do + let(:merge_request) do + create(:merge_request, source_project: project, target_project: project, description: '# Description header') + end + + context 'when a merge request does not have repository' do + let(:project) { create(:project, :public, :repository) } + + before do + visit(merge_request_path(merge_request)) + end + + it 'renders both the title and the description' do + node = find('.wiki h1 a#user-content-description-header') + expect(node[:href]).to end_with('#description-header') + + # Work around a weird Capybara behavior where calling `parent` on a node + # returns the whole document, not the node's actual parent element + expect(find(:xpath, "#{node.path}/..").text).to eq(merge_request.description[2..-1]) + + expect(page).to have_content(merge_request.title).and have_content(merge_request.description) + end + end + + context 'when a merge request has repository', :js do + let(:project) { create(:project, :public, :repository) } + + context 'when rendering description preview' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(edit_project_merge_request_path(project, merge_request)) + end + + it 'renders empty description preview' do + find('.gfm-form').fill_in(:merge_request_description, with: '') + + page.within('.gfm-form') do + click_link('Preview') + + expect(find('.js-md-preview')).to have_content('Nothing to preview.') + end + end + + it 'renders description preview' do + find('.gfm-form').fill_in(:merge_request_description, with: ':+1: Nice') + + page.within('.gfm-form') do + click_link('Preview') + + expect(find('.js-md-preview')).to have_css('gl-emoji') + end + + expect(find('.gfm-form')).to have_css('.js-md-preview').and have_link('Write') + expect(find('#merge_request_description', visible: false)).not_to be_visible + end + end + + context 'when the branch is rebased on the target' do + let(:merge_request) { create(:merge_request, :rebased, source_project: project, target_project: project) } + + before do + visit(merge_request_path(merge_request)) + end + + it 'does not show diverged commits count' do + page.within('.mr-source-target') do + expect(page).not_to have_content(/([0-9]+ commit[s]? behind)/) + end + end + end + + context 'when the branch is diverged on the target' do + let(:merge_request) { create(:merge_request, :diverged, source_project: project, target_project: project) } + + before do + visit(merge_request_path(merge_request)) + end + + it 'shows diverged commits count' do + page.within('.mr-source-target') do + expect(page).to have_content(/([0-9]+ commits behind)/) + end + end + end + end +end diff --git a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb new file mode 100644 index 00000000000..bf95dbb7d09 --- /dev/null +++ b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +describe 'User views open merge requests' do + set(:user) { create(:user) } + + shared_examples_for 'shows merge requests' do + it 'shows merge requests' do + expect(page).to have_content(project.name).and have_content(merge_request.source_project.name) + end + end + + context 'when project is public' do + set(:project) { create(:project, :public, :repository) } + + context 'when not signed in' do + context "when the target branch is the project's default branch" do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let!(:closed_merge_request) { create(:closed_merge_request, source_project: project, target_project: project) } + + before do + visit(project_merge_requests_path(project)) + end + + include_examples 'shows merge requests' + + it 'shows open merge requests' do + expect(page).to have_content(merge_request.title).and have_no_content(closed_merge_request.title) + end + + it 'does not show target branch name' do + expect(page).to have_content(merge_request.title) + expect(find('.issuable-info')).not_to have_content(project.default_branch) + end + end + + context "when the target branch is different from the project's default branch" do + let!(:merge_request) do + create(:merge_request, + source_project: project, + target_project: project, + source_branch: 'fix', + target_branch: 'feature_conflict') + end + + before do + visit(project_merge_requests_path(project)) + end + + it 'shows target branch name' do + expect(page).to have_content(merge_request.target_branch) + end + end + + context 'when a merge request has pipelines' do + let!(:build) { create :ci_build, pipeline: pipeline } + + let(:merge_request) do + create(:merge_request_with_diffs, + source_project: project, + target_project: project, + source_branch: 'merge-test') + end + + let(:pipeline) do + create(:ci_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + head_pipeline_of: merge_request) + end + + before do + project.enable_ci + + visit(project_merge_requests_path(project)) + end + + it 'shows pipeline status' do + page.within('.mr-list') do + expect(page).to have_link('Pipeline: pending') + end + end + end + end + + context 'when signed in' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_merge_requests_path(project)) + end + + include_examples 'shows merge requests' + end + end + + context 'when project is internal' do + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + set(:project) { create(:project, :internal, :repository) } + + context 'when signed in' do + before do + project.add_developer(user) + sign_in(user) + + visit(project_merge_requests_path(project)) + end + + include_examples 'shows merge requests' + end + end +end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index cd3dc72d3c6..6f097ad16c7 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -9,12 +9,14 @@ feature 'New project' do sign_in(user) end - it 'shows "New project" page' do + it 'shows "New project" page', :js do visit new_project_path expect(page).to have_content('Project path') expect(page).to have_content('Project name') + find('#import-project-tab').click + expect(page).to have_link('GitHub') expect(page).to have_link('Bitbucket') expect(page).to have_link('GitLab.com') @@ -23,14 +25,15 @@ feature 'New project' do expect(page).to have_link('GitLab export') end - context 'Visibility level selector' do + context 'Visibility level selector', :js do Gitlab::VisibilityLevel.options.each do |key, level| it "sets selector to #{key}" do stub_application_setting(default_project_visibility: level) visit new_project_path - - expect(find_field("project_visibility_level_#{level}")).to be_checked + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{level}")).to be_checked + end end it "saves visibility level #{level} on validation error" do @@ -38,8 +41,9 @@ feature 'New project' do choose(s_(key)) click_button('Create project') - - expect(find_field("project_visibility_level_#{level}")).to be_checked + page.within('#blank-project-pane') do + expect(find_field("project_visibility_level_#{level}")).to be_checked + end end end end @@ -51,9 +55,11 @@ feature 'New project' do end it 'selects the user namespace' do - namespace = find('#project_namespace_id') + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id') - expect(namespace.text).to eq user.username + expect(namespace.text).to eq user.username + end end end @@ -66,9 +72,11 @@ feature 'New project' do end it 'selects the group namespace' do - namespace = find('#project_namespace_id option[selected]') + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id option[selected]') - expect(namespace.text).to eq group.name + expect(namespace.text).to eq group.name + end end end @@ -82,9 +90,11 @@ feature 'New project' do end it 'selects the group namespace' do - namespace = find('#project_namespace_id option[selected]') + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id option[selected]') - expect(namespace.text).to eq subgroup.full_path + expect(namespace.text).to eq subgroup.full_path + end end end @@ -124,9 +134,10 @@ feature 'New project' do end end - context 'Import project options' do + context 'Import project options', :js do before do visit new_project_path + find('#import-project-tab').click end context 'from git repository url' do diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 24b335a7068..fa2f7a1fd78 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -54,7 +54,7 @@ feature 'Pipeline Schedules', :js do end it 'deletes the pipeline' do - click_link 'Delete' + accept_confirm { click_link 'Delete' } expect(page).not_to have_css(".pipeline-schedule-table-row") end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index acbc5b046e6..b8fa1a54c24 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -67,13 +67,13 @@ describe 'Pipeline', :js do it 'shows a running icon and a cancel action for the running build' do page.within('#ci-badge-deploy') do expect(page).to have_selector('.js-ci-status-icon-running') - expect(page).to have_selector('.js-icon-action-cancel') + expect(page).to have_selector('.js-icon-cancel') expect(page).to have_content('deploy') end end it 'should be possible to cancel the running build' do - find('#ci-badge-deploy .ci-action-icon-container').trigger('click') + find('#ci-badge-deploy .ci-action-icon-container').click expect(page).not_to have_content('Cancel running') end @@ -86,13 +86,13 @@ describe 'Pipeline', :js do expect(page).to have_content('build') end - page.within('#ci-badge-build .ci-action-icon-container') do - expect(page).to have_selector('.js-icon-action-retry') + page.within('#ci-badge-build .ci-action-icon-container.js-icon-retry') do + expect(page).to have_selector('svg') end end it 'should be possible to retry the success job' do - find('#ci-badge-build .ci-action-icon-container').trigger('click') + find('#ci-badge-build .ci-action-icon-container').click expect(page).not_to have_content('Retry job') end @@ -105,13 +105,13 @@ describe 'Pipeline', :js do expect(page).to have_content('test') end - page.within('#ci-badge-test .ci-action-icon-container') do - expect(page).to have_selector('.js-icon-action-retry') + page.within('#ci-badge-test .ci-action-icon-container.js-icon-retry') do + expect(page).to have_selector('svg') end end it 'should be possible to retry the failed build' do - find('#ci-badge-test .ci-action-icon-container').trigger('click') + find('#ci-badge-test .ci-action-icon-container').click expect(page).not_to have_content('Retry job') end @@ -124,13 +124,13 @@ describe 'Pipeline', :js do expect(page).to have_content('manual') end - page.within('#ci-badge-manual-build .ci-action-icon-container') do - expect(page).to have_selector('.js-icon-action-play') + page.within('#ci-badge-manual-build .ci-action-icon-container.js-icon-play') do + expect(page).to have_selector('svg') end end it 'should be possible to play the manual job' do - find('#ci-badge-manual-build .ci-action-icon-container').trigger('click') + find('#ci-badge-manual-build .ci-action-icon-container').click expect(page).not_to have_content('Play job') end @@ -165,7 +165,7 @@ describe 'Pipeline', :js do context 'when retrying' do before do - find('.js-retry-button').trigger('click') + find('.js-retry-button').click end it { expect(page).not_to have_content('Retry') } @@ -231,7 +231,7 @@ describe 'Pipeline', :js do context 'when retrying' do before do - find('.js-retry-button').trigger('click') + find('.js-retry-button').click end it { expect(page).not_to have_content('Retry') } diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f7b40cb1820..fc689bbb486 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -103,7 +103,7 @@ describe 'Pipelines', :js do context 'when canceling' do before do - find('.js-pipelines-cancel-button').click + accept_confirm { find('.js-pipelines-cancel-button').click } wait_for_requests end @@ -162,6 +162,16 @@ describe 'Pipelines', :js do expect(page).to have_selector( %Q{span[data-original-title="#{pipeline.yaml_errors}"]}) end + + it 'contains badge that indicates failure reason' do + expect(page).to have_content 'error' + end + + it 'contains badge with tooltip which contains failure reason' do + expect(pipeline.failure_reason?).to eq true + expect(page).to have_selector( + %Q{span[data-original-title="#{pipeline.present.failure_reason}"]}) + end end context 'with manual actions' do @@ -222,7 +232,7 @@ describe 'Pipelines', :js do context 'when canceling' do before do - find('.js-pipelines-cancel-button').trigger('click') + accept_alert { find('.js-pipelines-cancel-button').click } end it 'indicates that pipeline was canceled' do @@ -335,14 +345,14 @@ describe 'Pipelines', :js do context 'when clicking a stage badge' do it 'should open a dropdown' do - find('.js-builds-dropdown-button').trigger('click') + find('.js-builds-dropdown-button').click expect(page).to have_link build.name end it 'should be possible to cancel pending build' do - find('.js-builds-dropdown-button').trigger('click') - find('a.js-ci-action-icon').trigger('click') + find('.js-builds-dropdown-button').click + find('a.js-ci-action-icon').click expect(page).to have_content('canceled') expect(build.reload).to be_canceled @@ -351,11 +361,16 @@ describe 'Pipelines', :js do context 'dropdown jobs list' do it 'should keep the dropdown open when the user ctr/cmd + clicks in the job name' do - find('.js-builds-dropdown-button').trigger('click') - - execute_script('var e = $.Event("keydown", { keyCode: 64 }); $("body").trigger(e);') - - find('.mini-pipeline-graph-dropdown-item').trigger('click') + find('.js-builds-dropdown-button').click + dropdown_item = find('.mini-pipeline-graph-dropdown-item').native + + %i(alt control).each do |meta_key| + page.driver.browser.action + .key_down(meta_key) + .click(dropdown_item) + .key_up(meta_key) + .perform + end expect(page).to have_selector('.js-ci-action-icon') end @@ -443,7 +458,7 @@ describe 'Pipelines', :js do visit new_project_pipeline_path(project) end - context 'for valid commit', js: true do + context 'for valid commit', :js do before do click_button project.default_branch @@ -491,7 +506,7 @@ describe 'Pipelines', :js do end describe 'find pipelines' do - it 'shows filtered pipelines', js: true do + it 'shows filtered pipelines', :js do click_button project.default_branch page.within '.dropdown-menu' do @@ -515,7 +530,6 @@ describe 'Pipelines', :js do let(:project) { create(:project, :public, :repository) } it { expect(page).to have_content 'Build with confidence' } - it { expect(page).to have_http_status(:success) } end context 'when project is private' do diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 5d77cd1ccd5..15a5cd9990b 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -10,7 +10,7 @@ describe 'Edit Project Settings' do sign_in(user) end - describe 'Project settings section', js: true do + describe 'Project settings section', :js do it 'shows errors for invalid project name' do visit edit_project_path(project) fill_in 'project_name_edit', with: 'foo&bar' @@ -32,6 +32,32 @@ describe 'Edit Project Settings' do end end + describe 'Merge request settings section' do + it 'shows "Merge commit" strategy' do + visit edit_project_path(project) + + page.within '.merge-requests-feature' do + expect(page).to have_content 'Merge commit' + end + end + + it 'shows "Merge commit with semi-linear history " strategy' do + visit edit_project_path(project) + + page.within '.merge-requests-feature' do + expect(page).to have_content 'Merge commit with semi-linear history' + end + end + + it 'shows "Fast-forward merge" strategy' do + visit edit_project_path(project) + + page.within '.merge-requests-feature' do + expect(page).to have_content 'Fast-forward merge' + end + end + end + describe 'Rename repository section' do context 'with invalid characters' do it 'shows errors for invalid project path/name' do @@ -99,7 +125,7 @@ describe 'Edit Project Settings' do end end - describe 'Transfer project section', js: true do + describe 'Transfer project section', :js do let!(:project) { create(:project, :repository, namespace: user.namespace, name: 'gitlabhq') } let!(:group) { create(:group) } diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb index f0a23729220..33ccbc1a29f 100644 --- a/spec/features/projects/ref_switcher_spec.rb +++ b/spec/features/projects/ref_switcher_spec.rb @@ -1,11 +1,12 @@ require 'rails_helper' -feature 'Ref switcher', js: true do +feature 'Ref switcher', :js do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } before do project.team << [user, :master] + set_cookie('new_repo', 'true') sign_in(user) visit project_tree_path(project, 'master') end @@ -40,4 +41,38 @@ feature 'Ref switcher', js: true do expect(page).to have_title "'test'" end + + context "create branch" do + let(:input) { find('.js-new-branch-name') } + + before do + click_button 'master' + wait_for_requests + + page.within '.project-refs-form' do + find(".dropdown-footer-list a").click + end + end + + it "shows error message for the invalid branch name" do + input.set 'foo bar' + click_button('Create') + wait_for_requests + expect(page).to have_content 'Branch name is invalid' + end + + it "should create new branch properly" do + input.set 'new-branch-name' + click_button('Create') + wait_for_requests + expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name' + end + + it "should create new branch by Enter key" do + input.set 'new-branch-name-2' + input.native.send_keys :enter + wait_for_requests + expect(find('.js-project-refs-dropdown')).to have_content 'new-branch-name-2' + end + end end diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index 0a86292ae6c..ac78b1dfb1c 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -65,7 +65,7 @@ describe 'User activates Jira', :js do expect(find('.flash-container-page')).to have_content 'Test failed. message' expect(find('.flash-container-page')).to have_content 'Save anyway' - find('.flash-alert .flash-action').trigger('click') + find('.flash-alert .flash-action').click wait_for_requests expect(page).to have_content('JIRA activated.') diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb index 95d5e8b14b9..6f057137867 100644 --- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb @@ -76,7 +76,7 @@ feature 'Setup Mattermost slash commands', :js do select_element = find('#mattermost_team_id') selected_option = select_element.find('option[selected]') - expect(select_element['disabled']).to be(true) + expect(select_element['disabled']).to eq("true") expect(selected_option).to have_content(team_name.to_s) end @@ -104,7 +104,7 @@ feature 'Setup Mattermost slash commands', :js do select_element = find('#mattermost_team_id') - expect(select_element['disabled']).to be(false) + expect(select_element['disabled']).to be_falsey expect(select_element.all('option').count).to eq(3) end @@ -122,7 +122,7 @@ feature 'Setup Mattermost slash commands', :js do click_link 'Add to Mattermost' - expect(find('input[type="submit"]')['disabled']).not_to be(true) + expect(find('input[type="submit"]')['disabled']).not_to eq("true") end it 'disables the submit button if the required fields are not provided', :js do @@ -132,7 +132,7 @@ feature 'Setup Mattermost slash commands', :js do fill_in('mattermost_trigger', with: '') - expect(find('input[type="submit"]')['disabled']).to be(true) + expect(find('input[type="submit"]')['disabled']).to eq("true") end def stub_teams(count: 0) diff --git a/spec/features/projects/services/user_activates_packagist_spec.rb b/spec/features/projects/services/user_activates_packagist_spec.rb new file mode 100644 index 00000000000..b0cc818f093 --- /dev/null +++ b/spec/features/projects/services/user_activates_packagist_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'User activates Packagist' do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + + visit(project_settings_integrations_path(project)) + + click_link('Packagist') + end + + it 'activates service' do + check('Active') + fill_in('Username', with: 'theUser') + fill_in('Token', with: 'verySecret') + click_button('Save') + + expect(page).to have_content('Packagist activated.') + end +end diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb index f86591c2633..5c5e8b66642 100644 --- a/spec/features/projects/services/user_views_services_spec.rb +++ b/spec/features/projects/services/user_views_services_spec.rb @@ -21,5 +21,6 @@ describe 'User views services' do expect(page).to have_content('JetBrains TeamCity') expect(page).to have_content('Asana') expect(page).to have_content('Irker (IRC gateway)') + expect(page).to have_content('Packagist') end end diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb new file mode 100644 index 00000000000..28954a4fb40 --- /dev/null +++ b/spec/features/projects/settings/forked_project_settings_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Settings for a forked project', :js do + include ProjectForksHelper + let(:user) { create(:user) } + let(:original_project) { create(:project) } + let(:forked_project) { fork_project(original_project, user) } + + before do + original_project.add_master(user) + forked_project.add_master(user) + sign_in(user) + end + + shared_examples 'project settings for a forked projects' do + it 'allows deleting the link to the forked project' do + visit edit_project_path(forked_project) + + click_button 'Remove fork relationship' + + wait_for_requests + + fill_in('confirm_name_input', with: forked_project.name) + click_button('Confirm') + + expect(page).to have_content('The fork relationship has been removed.') + expect(forked_project.reload.forked?).to be_falsy + end + end + + it_behaves_like 'project settings for a forked projects' + + context 'when the original project is deleted' do + before do + original_project.destroy! + end + + it_behaves_like 'project settings for a forked projects' + end +end diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb index d932c4e4d9a..cbdb7973ac8 100644 --- a/spec/features/projects/settings/integration_settings_spec.rb +++ b/spec/features/projects/settings/integration_settings_spec.rb @@ -76,7 +76,7 @@ feature 'Integration settings' do expect(page).to have_content(url) end - scenario 'test existing webhook', js: true do + scenario 'test existing webhook', :js do WebMock.stub_request(:post, hook.url) visit integrations_path diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb index b1ec556bf16..ac76c30cc7c 100644 --- a/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -21,7 +21,7 @@ feature 'Project settings > Merge Requests', :js do within('.sharing-permissions-form') do find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click - click_on('Save changes') + find('input[value="Save changes"]').send_keys(:return) end expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds') @@ -41,7 +41,7 @@ feature 'Project settings > Merge Requests', :js do within('.sharing-permissions-form') do find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click - click_on('Save changes') + find('input[value="Save changes"]').send_keys(:return) end expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') @@ -62,7 +62,7 @@ feature 'Project settings > Merge Requests', :js do within('.sharing-permissions-form') do find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click - click_on('Save changes') + find('input[value="Save changes"]').send_keys(:return) end expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds') diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 975d204e75e..ea8f997409d 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -22,7 +22,7 @@ feature "Pipelines settings" do context 'for master' do given(:role) { :master } - scenario 'be allowed to change', js: true do + scenario 'be allowed to change' do fill_in('Test coverage parsing', with: 'coverage_regex') click_on 'Save changes' diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 15180d4b498..e2a5619c22b 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -23,7 +23,7 @@ feature 'Repository settings' do context 'for master' do given(:role) { :master } - context 'Deploy Keys', js: true do + context 'Deploy Keys', :js do let(:private_deploy_key) { create(:deploy_key, title: 'private_deploy_key', public: false) } let(:public_deploy_key) { create(:another_deploy_key, title: 'public_deploy_key', public: true) } let(:new_ssh_key) { attributes_for(:key)[:key] } @@ -34,7 +34,6 @@ feature 'Repository settings' do visit project_settings_repository_path(project) - expect(page.status_code).to eq(200) expect(page).to have_content('private_deploy_key') expect(page).to have_content('public_deploy_key') end @@ -86,7 +85,7 @@ feature 'Repository settings' do project.deploy_keys << private_deploy_key visit project_settings_repository_path(project) - find('li', text: private_deploy_key.title).click_button('Remove') + accept_confirm { find('li', text: private_deploy_key.title).click_button('Remove') } expect(page).not_to have_content(private_deploy_key.title) end diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index 37ee6255bd1..1c3b84d0114 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Visibility settings', js: true do +feature 'Visibility settings', :js do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) } diff --git a/spec/features/projects/show_project_spec.rb b/spec/features/projects/show_project_spec.rb index 1bc6fae9e7f..0b94c9eae5d 100644 --- a/spec/features/projects/show_project_spec.rb +++ b/spec/features/projects/show_project_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Project show page', feature: true do +describe 'Project show page', :feature do context 'when project pending delete' do let(:project) { create(:project, :empty_repo, pending_delete: true) } diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 3e79dba3f19..e4215291f99 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -10,7 +10,7 @@ feature 'Create Snippet', :js do fill_in 'project_snippet_title', with: 'My Snippet Title' fill_in 'project_snippet_description', with: 'My Snippet **Description**' page.within('.file-editor') do - find('.ace_editor').native.send_keys('Hello World!') + find('.ace_text-input', visible: false).send_keys('Hello World!') end end @@ -59,7 +59,7 @@ feature 'Create Snippet', :js do fill_form dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') - click_button('Create snippet') + find("input[value='Create snippet']").send_keys(:return) wait_for_requests expect(page).to have_content('My Snippet Title') diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb new file mode 100644 index 00000000000..8ee7b9cf015 --- /dev/null +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Multi-file editor new directory', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + project.add_master(user) + sign_in(user) + + set_cookie('new_repo', 'true') + + visit project_tree_path(project, :master) + + wait_for_requests + end + + it 'creates directory in current directory' do + find('.add-to-tree').click + + click_link('New directory') + + page.within('.popup-dialog') do + find('.form-control').set('foldername') + + click_button('Create directory') + end + + fill_in('commit-message', with: 'commit message') + + click_button('Commit 1 file') + + expect(page).to have_selector('td', text: 'commit message') + + click_link('foldername') + + expect(page).to have_selector('td', text: 'commit message', count: 2) + expect(page).to have_selector('td', text: '.gitkeep') + end +end diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb new file mode 100644 index 00000000000..1e2de0711b8 --- /dev/null +++ b/spec/features/projects/tree/create_file_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +feature 'Multi-file editor new file', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + project.add_master(user) + sign_in(user) + + set_cookie('new_repo', 'true') + + visit project_tree_path(project, :master) + + wait_for_requests + end + + it 'creates file in current directory' do + find('.add-to-tree').click + + click_link('New file') + + page.within('.popup-dialog') do + find('.form-control').set('filename') + + click_button('Create file') + end + + fill_in('commit-message', with: 'commit message') + + click_button('Commit 1 file') + + expect(page).to have_selector('td', text: 'commit message') + end +end diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb new file mode 100644 index 00000000000..8439bb5a69e --- /dev/null +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +feature 'Multi-file editor upload file', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') } + let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') } + + before do + project.add_master(user) + sign_in(user) + + set_cookie('new_repo', 'true') + + visit project_tree_path(project, :master) + + wait_for_requests + end + + it 'uploads text file' do + find('.add-to-tree').click + + # make the field visible so capybara can use it + execute_script('document.querySelector("#file-upload").classList.remove("hidden")') + attach_file('file-upload', txt_file) + + find('.add-to-tree').click + + expect(page).to have_selector('.repo-tab', text: 'doc_sample.txt') + expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) + end + + it 'uploads image file' do + find('.add-to-tree').click + + # make the field visible so capybara can use it + execute_script('document.querySelector("#file-upload").classList.remove("hidden")') + attach_file('file-upload', img_file) + + find('.add-to-tree').click + + expect(page).to have_selector('.repo-tab', text: 'dk.png') + expect(page).not_to have_selector('.monaco-editor') + expect(page).to have_content('The source could not be displayed for this temporary file.') + end +end diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb index b7a0b72db50..f5e4d7f5130 100644 --- a/spec/features/projects/user_browses_files_spec.rb +++ b/spec/features/projects/user_browses_files_spec.rb @@ -76,7 +76,7 @@ describe 'User browses files' do expect(page).to have_content('LICENSE') end - it 'shows files from a repository with apostroph in its name', js: true do + it 'shows files from a repository with apostroph in its name', :js do first('.js-project-refs-dropdown').click page.within('.project-refs-form') do @@ -91,7 +91,7 @@ describe 'User browses files' do expect(page).not_to have_content('Loading commit data...') end - it 'shows the code with a leading dot in the directory', js: true do + it 'shows the code with a leading dot in the directory', :js do first('.js-project-refs-dropdown').click page.within('.project-refs-form') do @@ -117,7 +117,7 @@ describe 'User browses files' do click_link('.gitignore') end - it 'shows a file content', js: true do + it 'shows a file content', :js do wait_for_requests expect(page).to have_content('*.rbc') end @@ -168,17 +168,18 @@ describe 'User browses files' do visit(tree_path_root_ref) end - it 'shows a preview of a file content', js: true do + it 'shows a preview of a file content', :js do find('.add-to-tree').click click_link('Upload file') drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')) page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Upload file') end - fill_in(:branch_name, with: 'new_branch_name', visible: true) - click_button('Upload file') + wait_for_all_requests visit(project_blob_path(project, 'new_branch_name/logo_sample.svg')) diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb index 1ba5d83eadf..052cb3188c5 100644 --- a/spec/features/projects/user_creates_directory_spec.rb +++ b/spec/features/projects/user_creates_directory_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'User creates a directory', js: true do +feature 'User creates a directory', :js do let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." @@ -79,7 +79,7 @@ feature 'User creates a directory', js: true do fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Create directory') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) end diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb index 3d335687510..d84b91ddc32 100644 --- a/spec/features/projects/user_creates_files_spec.rb +++ b/spec/features/projects/user_creates_files_spec.rb @@ -59,7 +59,8 @@ describe 'User creates files' do expect(page).to have_selector('.file-editor') end - it 'creates and commit a new file', js: true do + it 'creates and commit a new file', :js do + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:file_name, with: 'not_a_file.md') fill_in(:commit_message, with: 'New commit message', visible: true) @@ -74,7 +75,8 @@ describe 'User creates files' do expect(page).to have_content('*.rbca') end - it 'creates and commit a new file with new lines at the end of file', js: true do + it 'creates and commit a new file with new lines at the end of file', :js do + find('#editor') execute_script('ace.edit("editor").setValue("Sample\n\n\n")') fill_in(:file_name, with: 'not_a_file.md') fill_in(:commit_message, with: 'New commit message', visible: true) @@ -86,14 +88,16 @@ describe 'User creates files' do find('.js-edit-blob').click + find('#editor') expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n") end - it 'creates and commit a new file with a directory name', js: true do + it 'creates and commit a new file with a directory name', :js do fill_in(:file_name, with: 'foo/bar/baz.txt') expect(page).to have_selector('.file-editor') + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') @@ -105,9 +109,10 @@ describe 'User creates files' do expect(page).to have_content('*.rbca') end - it 'creates and commit a new file specifying a new branch', js: true do + it 'creates and commit a new file specifying a new branch', :js do expect(page).to have_selector('.file-editor') + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:file_name, with: 'not_a_file.md') fill_in(:commit_message, with: 'New commit message', visible: true) @@ -130,19 +135,20 @@ describe 'User creates files' do visit(project2_tree_path_root_ref) end - it 'creates and commit new file in forked project', js: true do + it 'creates and commit new file in forked project', :js do find('.add-to-tree').click click_link('New file') expect(page).to have_selector('.file-editor') + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:file_name, with: 'not_a_file.md') fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) expect(page).to have_content('New commit message') diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb index 1c3791f63ac..4a152572502 100644 --- a/spec/features/projects/user_creates_project_spec.rb +++ b/spec/features/projects/user_creates_project_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'User creates a project', js: true do +feature 'User creates a project', :js do let(:user) { create(:user) } before do diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb index 95cd316be0e..9e4e92ec076 100644 --- a/spec/features/projects/user_deletes_files_spec.rb +++ b/spec/features/projects/user_deletes_files_spec.rb @@ -21,7 +21,7 @@ describe 'User deletes files' do visit(project_tree_path_root_ref) end - it 'deletes the file', js: true do + it 'deletes the file', :js do click_link('.gitignore') expect(page).to have_content('.gitignore') @@ -41,7 +41,7 @@ describe 'User deletes files' do visit(project2_tree_path_root_ref) end - it 'deletes the file in a forked project', js: true do + it 'deletes the file in a forked project', :js do click_link('.gitignore') expect(page).to have_content('.gitignore') @@ -59,7 +59,7 @@ describe 'User deletes files' do fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Delete file') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) expect(page).to have_content('New commit message') diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb index 19954313c23..d26ee653415 100644 --- a/spec/features/projects/user_edits_files_spec.rb +++ b/spec/features/projects/user_edits_files_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe 'User edits files' do + include ProjectForksHelper let(:project) { create(:project, :repository, name: 'Shop') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } @@ -17,11 +18,12 @@ describe 'User edits files' do visit(project_tree_path_root_ref) end - it 'inserts a content of a file', js: true do + it 'inserts a content of a file', :js do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') @@ -34,11 +36,12 @@ describe 'User edits files' do expect(page).not_to have_link('edit') end - it 'commits an edited file', js: true do + it 'commits an edited file', :js do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') @@ -50,12 +53,13 @@ describe 'User edits files' do expect(page).to have_content('*.rbca') end - it 'commits an edited file to a new branch', js: true do + it 'commits an edited file to a new branch', :js do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:branch_name, with: 'new_branch_name', visible: true) @@ -68,11 +72,12 @@ describe 'User edits files' do expect(page).to have_content('*.rbca') end - it 'shows the diff of an edited file', js: true do + it 'shows the diff of an edited file', :js do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") click_link('Preview changes') @@ -86,7 +91,7 @@ describe 'User edits files' do visit(project2_tree_path_root_ref) end - it 'inserts a content of a file in a forked project', js: true do + it 'inserts a content of a file in a forked project', :js do click_link('.gitignore') find('.js-edit-blob').click @@ -102,12 +107,13 @@ describe 'User edits files' do find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') end - it 'commits an edited file in a forked project', js: true do + it 'commits an edited file in a forked project', :js do click_link('.gitignore') find('.js-edit-blob').click @@ -118,11 +124,12 @@ describe 'User edits files' do find('.file-editor', match: :first) + find('#editor') execute_script("ace.edit('editor').setValue('*.rbca')") fill_in(:commit_message, with: 'New commit message', visible: true) click_button('Commit changes') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) @@ -130,5 +137,35 @@ describe 'User edits files' do expect(page).to have_content('New commit message') end + + context 'when the user already had a fork of the project', :js do + let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) } + before do + visit(project2_tree_path_root_ref) + end + + it 'links to the forked project for editing' do + click_link('.gitignore') + find('.js-edit-blob').click + + expect(page).not_to have_link('Fork') + expect(page).not_to have_button('Cancel') + + find('#editor') + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'Another commit', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + wait_for_requests + + expect(page).to have_content('Another commit') + expect(page).to have_content("From #{forked_project.full_path}") + expect(page).to have_content("into #{project2.full_path}") + end + end end end diff --git a/spec/features/projects/user_interacts_with_stars_spec.rb b/spec/features/projects/user_interacts_with_stars_spec.rb index 0ac3f8181fa..d9d2e0ab171 100644 --- a/spec/features/projects/user_interacts_with_stars_spec.rb +++ b/spec/features/projects/user_interacts_with_stars_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'User interacts with project stars' do let(:project) { create(:project, :public, :repository) } - context 'when user is signed in', js: true do + context 'when user is signed in', :js do let(:user) { create(:user) } before do diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb index e284fdefd4f..245b6aa285b 100644 --- a/spec/features/projects/user_replaces_files_spec.rb +++ b/spec/features/projects/user_replaces_files_spec.rb @@ -23,7 +23,7 @@ describe 'User replaces files' do visit(project_tree_path_root_ref) end - it 'replaces an existed file with a new one', js: true do + it 'replaces an existed file with a new one', :js do click_link('.gitignore') expect(page).to have_content('.gitignore') @@ -49,7 +49,7 @@ describe 'User replaces files' do visit(project2_tree_path_root_ref) end - it 'replaces an existed file with a new one in a forked project', js: true do + it 'replaces an existed file with a new one in a forked project', :js do click_link('.gitignore') expect(page).to have_content('.gitignore') @@ -74,7 +74,7 @@ describe 'User replaces files' do expect(page).to have_content('Replacement file commit message') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb index 98871317ca3..ae51901adc6 100644 --- a/spec/features/projects/user_uploads_files_spec.rb +++ b/spec/features/projects/user_uploads_files_spec.rb @@ -23,7 +23,7 @@ describe 'User uploads files' do visit(project_tree_path_root_ref) end - it 'uploads and commit a new file', js: true do + it 'uploads and commit a new file', :js do find('.add-to-tree').click click_link('Upload file') drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) @@ -39,6 +39,9 @@ describe 'User uploads files' do expect(current_path).to eq(project_new_merge_request_path(project)) click_link('Changes') + find("a[data-action='diffs']", text: 'Changes').click + + wait_for_requests expect(page).to have_content('Lorem ipsum dolor sit amet') expect(page).to have_content('Sed ut perspiciatis unde omnis') @@ -51,7 +54,7 @@ describe 'User uploads files' do visit(project2_tree_path_root_ref) end - it 'uploads and commit a new fileto a forked project', js: true do + it 'uploads and commit a new file to a forked project', :js do find('.add-to-tree').click click_link('Upload file') @@ -69,11 +72,13 @@ describe 'User uploads files' do expect(page).to have_content('New commit message') - fork = user.fork_of(project2) + fork = user.fork_of(project2.reload) expect(current_path).to eq(project_new_merge_request_path(fork)) - click_link('Changes') + find("a[data-action='diffs']", text: 'Changes').click + + wait_for_requests expect(page).to have_content('Lorem ipsum dolor sit amet') expect(page).to have_content('Sed ut perspiciatis unde omnis') diff --git a/spec/features/projects/user_views_details_spec.rb b/spec/features/projects/user_views_details_spec.rb new file mode 100644 index 00000000000..ffc063654cd --- /dev/null +++ b/spec/features/projects/user_views_details_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +describe 'User views details' do + set(:user) { create(:user) } + + shared_examples_for 'redirects to the sign in page' do + it 'redirects to the sign in page' do + expect(current_path).to eq(new_user_session_path) + end + end + + shared_examples_for 'shows details of empty project' do + let(:user_has_ssh_key) { false } + + it 'shows details' do + expect(page).not_to have_content('Git global setup') + + page.all(:css, '.git-empty .clone').each do |element| + expect(element.text).to include(project.http_url_to_repo) + end + + expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + end + end + + shared_examples_for 'shows details of non empty project' do + let(:user_has_ssh_key) { false } + + it 'shows details' do + page.within('.breadcrumbs .breadcrumb-item-text') do + expect(page).to have_content(project.title) + end + + expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key + end + end + + context 'when project is public' do + context 'when project is empty' do + set(:project) { create(:project_empty_repo, :public) } + + context 'when not signed in' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of empty project' + end + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when user does not have ssh keys' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of empty project' + end + + context 'when user has ssh keys' do + before do + create(:personal_key, user: user) + + visit(project_path(project)) + end + + include_examples 'shows details of empty project' do + let(:user_has_ssh_key) { true } + end + end + end + end + + context 'when project is not empty' do + set(:project) { create(:project, :public, :repository) } + + before do + visit(project_path(project)) + end + + context 'when not signed in' do + before do + allow(Gitlab.config.gitlab).to receive(:host).and_return('www.example.com') + end + + include_examples 'shows details of non empty project' + end + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when user does not have ssh keys' do + before do + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' + end + + context 'when user has ssh keys' do + before do + create(:personal_key, user: user) + + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' do + let(:user_has_ssh_key) { true } + end + end + end + end + end + + context 'when project is internal' do + set(:project) { create(:project, :internal, :repository) } + + context 'when not signed in' do + before do + visit(project_path(project)) + end + + include_examples 'redirects to the sign in page' + end + + context 'when signed in' do + before do + sign_in(user) + + visit(project_path(project)) + end + + include_examples 'shows details of non empty project' + end + end + + context 'when project is private' do + set(:project) { create(:project, :private) } + + before do + visit(project_path(project)) + end + + include_examples 'redirects to the sign in page' + end +end diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb index 2a316a0d0db..7f547a4ca1f 100644 --- a/spec/features/projects/view_on_env_spec.rb +++ b/spec/features/projects/view_on_env_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'View on environment', js: true do +describe 'View on environment', :js do let(:branch_name) { 'feature' } let(:file_path) { 'files/ruby/feature.rb' } let(:project) { create(:project, :repository) } diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 9a4ccf3c54d..337baaf4dcd 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Projects > Wiki > User previews markdown changes', js: true do +feature 'Projects > Wiki > User previews markdown changes', :js do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } let(:wiki_content) do @@ -14,18 +14,17 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do background do project.team << [user, :master] - WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute sign_in(user) visit project_path(project) - find('.shortcuts-wiki').trigger('click') + find('.shortcuts-wiki').click end context "while creating a new wiki page" do context "when there are no spaces or hyphens in the page name" do it "rewrites relative links as expected" do - find('.add-new-wiki').trigger('click') + find('.add-new-wiki').click page.within '#modal-new-wiki' do fill_in :new_wiki_path, with: 'a/b/c/d' click_button 'Create page' @@ -92,7 +91,7 @@ feature 'Projects > Wiki > User previews markdown changes', js: true do context "while editing a wiki page" do def create_wiki_page(path) - find('.add-new-wiki').trigger('click') + find('.add-new-wiki').click page.within '#modal-new-wiki' do fill_in :new_wiki_path, with: path diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb index eaff5f876b6..f70d1e710dd 100644 --- a/spec/features/projects/wiki/shortcuts_spec.rb +++ b/spec/features/projects/wiki/shortcuts_spec.rb @@ -3,9 +3,7 @@ require 'spec_helper' feature 'Wiki shortcuts', :js do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } - let(:wiki_page) do - WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute - end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: 'Home page' }) } before do sign_in(user) diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index e72b7dc0dd5..4a9d1cb87e1 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -77,14 +77,14 @@ describe 'User creates wiki page' do [stem] ++++ - \sqrt{4} = 2 + \\sqrt{4} = 2 ++++ another part [latexmath] ++++ - \beta_x \gamma + \\beta_x \\gamma ++++ stem:[2+2] is 4 diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb index 9a92622ba2b..37a118c34ab 100644 --- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb @@ -3,14 +3,7 @@ require 'spec_helper' describe 'Projects > Wiki > User views Git access wiki page' do let(:user) { create(:user) } let(:project) { create(:project, :public) } - let(:wiki_page) do - WikiPages::CreateService.new( - project, - user, - title: 'home', - content: '[some link](other-page)' - ).execute - end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) } before do sign_in(user) diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index 1cf14204159..949d90a50ff 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -40,7 +40,7 @@ describe 'User updates wiki page' do expect(current_path).to include('one/two/three-test') expect(find('.wiki-pages')).to have_content('Three') - click_on('Three') + first(:link, text: 'Three').click expect(find('.nav-text')).to have_content('Three') diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb index cf9fe4c1ad1..ebb3bd044c1 100644 --- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb @@ -18,12 +18,7 @@ describe 'Projects > Wiki > User views wiki in project page' do context 'when wiki homepage contains a link' do before do - WikiPages::CreateService.new( - project, - user, - title: 'home', - content: '[some link](other-page)' - ).execute + create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) end it 'displays the correct URL for the link' do diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb index d201d4f6b98..ff325aeadd3 100644 --- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb @@ -34,7 +34,7 @@ describe 'User views a wiki page' do it 'shows the history of a page that has a path', :js do expect(current_path).to include('one/two/three-test') - click_on('Three') + first(:link, text: 'Three').click click_on('Page history') expect(current_path).to include('one/two/three-test') @@ -48,7 +48,7 @@ describe 'User views a wiki page' do expect(current_path).to include('one/two/three-test') expect(find('.wiki-pages')).to have_content('Three') - click_on('Three') + first(:link, text: 'Three').click expect(find('.nav-text')).to have_content('Three') @@ -81,10 +81,15 @@ describe 'User views a wiki page' do end it 'shows a file stored in a page' do - file = Gollum::File.new(project.wiki) + gollum_file_double = double('Gollum::File', + mime_type: 'image/jpeg', + name: 'images/image.jpg', + path: 'images/image.jpg', + raw_data: '') + wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) - allow_any_instance_of(Gollum::Wiki).to receive(:file).with('image.jpg', 'master', true).and_return(file) - allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return('image/jpeg') + allow(wiki_file).to receive(:mime_type).and_return('image/jpeg') + allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file) expect(page).to have_xpath('//img[@data-src="image.jpg"]') expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg") @@ -133,7 +138,7 @@ describe 'User views a wiki page' do it 'opens a default wiki page', :js do visit(project_path(project)) - find('.shortcuts-wiki').trigger('click') + find('.shortcuts-wiki').click expect(page).to have_content('Home · Create Page') end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 81f7ab80a04..63e6051b571 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Project' do + include ProjectForksHelper + describe 'creating from template' do let(:user) { create(:user) } let(:template) { Gitlab::ProjectTemplate.find(:rails) } @@ -10,8 +12,9 @@ feature 'Project' do visit new_project_path end - it "allows creation from templates" do - page.choose(template.name) + it "allows creation from templates", :js do + find('#create-from-template-tab').click + find("label[for=#{template.name}]").click fill_in("project_path", with: template.name) page.within '#content-body' do @@ -55,13 +58,12 @@ feature 'Project' do end end - describe 'remove forked relationship', js: true do + describe 'remove forked relationship', :js do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { fork_project(create(:project, :public), user, namespace_id: user.namespace) } before do sign_in user - create(:forked_project_link, forked_to_project: project) visit edit_project_path(project) end @@ -71,12 +73,61 @@ feature 'Project' do remove_with_confirm('Remove fork relationship', project.path) expect(page).to have_content 'The fork relationship has been removed.' - expect(project.forked?).to be_falsey + expect(project.reload.forked?).to be_falsey expect(page).not_to have_content 'Remove fork relationship' end end - describe 'removal', js: true do + describe 'showing information about source of a project fork' do + let(:user) { create(:user) } + let(:base_project) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(base_project, user, repository: true) } + + before do + sign_in user + end + + it 'shows a link to the source project when it is available' do + visit project_path(forked_project) + + expect(page).to have_content('Forked from') + expect(page).to have_link(base_project.full_name) + end + + it 'does not contain fork network information for the root project' do + forked_project + + visit project_path(base_project) + + expect(page).not_to have_content('In fork network of') + expect(page).not_to have_content('Forked from') + end + + it 'shows the name of the deleted project when the source was deleted' do + forked_project + Projects::DestroyService.new(base_project, base_project.owner).execute + + visit project_path(forked_project) + + expect(page).to have_content("Forked from #{base_project.full_name} (deleted)") + end + + context 'a fork of a fork' do + let(:fork_of_fork) { fork_project(forked_project, user, repository: true) } + + it 'links to the base project if the source project is removed' do + fork_of_fork + Projects::DestroyService.new(forked_project, user).execute + + visit project_path(fork_of_fork) + + expect(page).to have_content("Forked from") + expect(page).to have_link(base_project.full_name) + end + end + end + + describe 'removal', :js do let(:user) { create(:user, username: 'test', name: 'test') } let(:project) { create(:project, namespace: user.namespace, name: 'project1') } @@ -88,7 +139,7 @@ feature 'Project' do it 'removes a project' do expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1) - expect(page).to have_content "Project 'test / project1' will be deleted." + expect(page).to have_content "Project 'test / project1' is in the process of being deleted." expect(Project.all.count).to be_zero expect(project.issues).to be_empty expect(project.merge_requests).to be_empty diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 3677bf38724..a4084818284 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -1,93 +1,178 @@ require 'spec_helper' -feature 'Protected Branches', js: true do - let(:user) { create(:user, :admin) } +feature 'Protected Branches', :js do + let(:user) { create(:user) } + let(:admin) { create(:admin) } let(:project) { create(:project, :repository) } - before do - sign_in(user) - end + context 'logged in as developer' do + before do + project.add_developer(user) + sign_in(user) + end - def set_protected_branch_name(branch_name) - find(".js-protected-branch-select").trigger('click') - find(".dropdown-input-field").set(branch_name) - click_on("Create wildcard #{branch_name}") - end + describe 'Delete protected branch' do + before do + create(:protected_branch, project: project, name: 'fix') + expect(ProtectedBranch.count).to eq(1) + end + + it 'does not allow developer to removes protected branch' do + visit project_branches_path(project) - describe "explicit protected branches" do - it "allows creating explicit protected branches" do - visit project_protected_branches_path(project) - set_protected_branch_name('some-branch') - click_on "Protect" + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_css('.btn-remove.disabled') + end + end + end - within(".protected-branches-list") { expect(page).to have_content('some-branch') } - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.name).to eq('some-branch') + context 'logged in as master' do + before do + project.add_master(user) + sign_in(user) end - it "displays the last commit on the matching branch if it exists" do - commit = create(:commit, project: project) - project.repository.add_branch(user, 'some-branch', commit.id) + describe 'Delete protected branch' do + before do + create(:protected_branch, project: project, name: 'fix') + expect(ProtectedBranch.count).to eq(1) + end - visit project_protected_branches_path(project) - set_protected_branch_name('some-branch') - click_on "Protect" + it 'removes branch after modal confirmation' do + visit project_branches_path(project) - within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) } - end + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_content('fix') + expect(find('.all-branches')).to have_selector('li', count: 1) + page.find('[data-target="#modal-delete-branch"]').click - it "displays an error message if the named branch does not exist" do - visit project_protected_branches_path(project) - set_protected_branch_name('some-branch') - click_on "Protect" + expect(page).to have_css('.js-delete-branch[disabled]') + fill_in 'delete_branch_input', with: 'fix' + click_link 'Delete protected branch' - within(".protected-branches-list") { expect(page).to have_content('branch was removed') } + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_content('No branches to show') + end end - end - describe "wildcard protected branches" do - it "allows creating protected branches with a wildcard" do - visit project_protected_branches_path(project) - set_protected_branch_name('*-stable') - click_on "Protect" + describe "Saved defaults" do + it "keeps the allowed to merge and push dropdowns defaults based on the previous selection" do + visit project_protected_branches_path(project) + form = '.js-new-protected-branch' + + within form do + find(".js-allowed-to-merge").click + click_link 'No one' + find(".js-allowed-to-push").click + click_link 'Developers + Masters' + end + + visit project_protected_branches_path(project) + + within form do + page.within(".js-allowed-to-merge") do + expect(page.find(".dropdown-toggle-text")).to have_content("No one") + end + page.within(".js-allowed-to-push") do + expect(page.find(".dropdown-toggle-text")).to have_content("Developers + Masters") + end + end + end + end + end - within(".protected-branches-list") { expect(page).to have_content('*-stable') } - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.name).to eq('*-stable') + context 'logged in as admin' do + before do + sign_in(admin) end - it "displays the number of matching branches" do - project.repository.add_branch(user, 'production-stable', 'master') - project.repository.add_branch(user, 'staging-stable', 'master') + describe "explicit protected branches" do + it "allows creating explicit protected branches" do + visit project_protected_branches_path(project) + set_protected_branch_name('some-branch') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content('some-branch') } + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.name).to eq('some-branch') + end + + it "displays the last commit on the matching branch if it exists" do + commit = create(:commit, project: project) + project.repository.add_branch(admin, 'some-branch', commit.id) + + visit project_protected_branches_path(project) + set_protected_branch_name('some-branch') + click_on "Protect" - visit project_protected_branches_path(project) - set_protected_branch_name('*-stable') - click_on "Protect" + within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) } + end + + it "displays an error message if the named branch does not exist" do + visit project_protected_branches_path(project) + set_protected_branch_name('some-branch') + click_on "Protect" - within(".protected-branches-list") { expect(page).to have_content("2 matching branches") } + within(".protected-branches-list") { expect(page).to have_content('branch was removed') } + end end - it "displays all the branches matching the wildcard" do - project.repository.add_branch(user, 'production-stable', 'master') - project.repository.add_branch(user, 'staging-stable', 'master') - project.repository.add_branch(user, 'development', 'master') + describe "wildcard protected branches" do + it "allows creating protected branches with a wildcard" do + visit project_protected_branches_path(project) + set_protected_branch_name('*-stable') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content('*-stable') } + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.name).to eq('*-stable') + end + + it "displays the number of matching branches" do + project.repository.add_branch(admin, 'production-stable', 'master') + project.repository.add_branch(admin, 'staging-stable', 'master') + + visit project_protected_branches_path(project) + set_protected_branch_name('*-stable') + click_on "Protect" + + within(".protected-branches-list") { expect(page).to have_content("2 matching branches") } + end + + it "displays all the branches matching the wildcard" do + project.repository.add_branch(admin, 'production-stable', 'master') + project.repository.add_branch(admin, 'staging-stable', 'master') + project.repository.add_branch(admin, 'development', 'master') - visit project_protected_branches_path(project) - set_protected_branch_name('*-stable') - click_on "Protect" + visit project_protected_branches_path(project) + set_protected_branch_name('*-stable') + click_on "Protect" - visit project_protected_branches_path(project) - click_on "2 matching branches" + visit project_protected_branches_path(project) + click_on "2 matching branches" - within(".protected-branches-list") do - expect(page).to have_content("production-stable") - expect(page).to have_content("staging-stable") - expect(page).not_to have_content("development") + within(".protected-branches-list") do + expect(page).to have_content("production-stable") + expect(page).to have_content("staging-stable") + expect(page).not_to have_content("development") + end end end + + describe "access control" do + include_examples "protected branches > access control > CE" + end end - describe "access control" do - include_examples "protected branches > access control > CE" + def set_protected_branch_name(branch_name) + find(".js-protected-branch-select").click + find(".dropdown-input-field").set(branch_name) + click_on("Create wildcard #{branch_name}") end end diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index 8abd4403065..8cc6f17b8d9 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Protected Tags', js: true do +feature 'Protected Tags', :js do let(:user) { create(:user, :admin) } let(:project) { create(:project, :repository) } diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb index b1f51959d54..74890c86047 100644 --- a/spec/features/raven_js_spec.rb +++ b/spec/features/raven_js_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'RavenJS', :js do +feature 'RavenJS' do let(:raven_path) { '/raven.bundle.js' } it 'should not load raven if sentry is disabled' do @@ -18,6 +18,8 @@ feature 'RavenJS', :js do end def has_requested_raven - page.driver.network_traffic.one? {|request| request.url.end_with?(raven_path)} + page.all('script', visible: false).one? do |elm| + elm[:src] =~ /#{raven_path}$/ + end end end diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index 0ed797a62ea..77212fb105b 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -32,14 +32,14 @@ describe 'User searches for code' do include_examples 'top right search form' it 'finds code' do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click page.within('.project-filter') do click_link(project.name_with_namespace) end fill_in('dashboard_search', with: 'rspec') - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.results') do expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions') diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb index 630a81b1c5e..ef9553f2a91 100644 --- a/spec/features/search/user_searches_for_issues_spec.rb +++ b/spec/features/search/user_searches_for_issues_spec.rb @@ -18,7 +18,7 @@ describe 'User searches for issues', :js do it 'finds an issue' do fill_in('dashboard_search', with: issue1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Issues') @@ -31,14 +31,14 @@ describe 'User searches for issues', :js do context 'when on a project page' do it 'finds an issue' do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click page.within('.project-filter') do click_link(project.name_with_namespace) end fill_in('dashboard_search', with: issue1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Issues') @@ -62,7 +62,7 @@ describe 'User searches for issues', :js do it 'finds an issue' do fill_in('dashboard_search', with: issue1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Issues') diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb index 116256682f4..3b6739aecbd 100644 --- a/spec/features/search/user_searches_for_merge_requests_spec.rb +++ b/spec/features/search/user_searches_for_merge_requests_spec.rb @@ -17,7 +17,7 @@ describe 'User searches for merge requests', :js do it 'finds a merge request' do fill_in('dashboard_search', with: merge_request1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Merge requests') @@ -30,14 +30,14 @@ describe 'User searches for merge requests', :js do context 'when on a project page' do it 'finds a merge request' do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click page.within('.project-filter') do click_link(project.name_with_namespace) end fill_in('dashboard_search', with: merge_request1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Merge requests') diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb index 4fa9fe9ce8c..6e197aee498 100644 --- a/spec/features/search/user_searches_for_milestones_spec.rb +++ b/spec/features/search/user_searches_for_milestones_spec.rb @@ -17,7 +17,7 @@ describe 'User searches for milestones', :js do it 'finds a milestone' do fill_in('dashboard_search', with: milestone1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Milestones') @@ -30,14 +30,14 @@ describe 'User searches for milestones', :js do context 'when on a project page' do it 'finds a milestone' do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click page.within('.project-filter') do click_link(project.name_with_namespace) end fill_in('dashboard_search', with: milestone1.title) - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Milestones') diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb index 1ea56479ecc..00af625dc86 100644 --- a/spec/features/search/user_searches_for_wiki_pages_spec.rb +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -15,14 +15,14 @@ describe 'User searches for wiki pages', :js do include_examples 'top right search form' it 'finds a page' do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click page.within('.project-filter') do click_link(project.name_with_namespace) end fill_in('dashboard_search', with: 'content') - find('.btn-search').trigger('click') + find('.btn-search').click page.within('.search-filter') do click_link('Wiki') diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index 95f3eb5e805..aa883c964d2 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -16,7 +16,7 @@ describe 'User uses search filters', :js do context' when filtering by group' do it 'shows group projects' do - find('.js-search-group-dropdown').trigger('click') + find('.js-search-group-dropdown').click wait_for_requests @@ -27,7 +27,7 @@ describe 'User uses search filters', :js do expect(find('.js-search-group-dropdown')).to have_content(group.name) page.within('.project-filter') do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click wait_for_requests @@ -39,7 +39,7 @@ describe 'User uses search filters', :js do context' when filtering by project' do it 'shows a project' do page.within('.project-filter') do - find('.js-search-project-dropdown').trigger('click') + find('.js-search-project-dropdown').click wait_for_requests diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index b6367b88e17..917fad74ef1 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -24,6 +24,24 @@ feature 'Signup' do end end + context "when sigining up with different cased emails" do + it "creates the user successfully" do + user = build(:user) + + visit root_path + + fill_in 'new_user_name', with: user.name + fill_in 'new_user_username', with: user.username + fill_in 'new_user_email', with: user.email + fill_in 'new_user_email_confirmation', with: user.email.capitalize + fill_in 'new_user_password', with: user.password + click_button "Register" + + expect(current_path).to eq dashboard_projects_path + expect(page).to have_content("Welcome! You have signed up successfully.") + end + end + context "when not sending confirmation email" do before do stub_application_setting(send_user_confirmation_email: false) diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index 3a229612235..3a2768c424f 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Internal Snippets', js: true do +feature 'Internal Snippets', :js do let(:internal_snippet) { create(:personal_snippet, :internal) } describe 'normal user' do diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index bf79974b8c6..269351e55c9 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -74,24 +74,21 @@ describe 'Comments on personal snippets', :js do it 'should not have autocomplete' do wait_for_requests - request_count_before = page.driver.network_traffic.count find('#note_note').native.send_keys('') fill_in 'note[note]', with: '@' wait_for_requests - request_count_after = page.driver.network_traffic.count # This selector probably won't be in place even if autocomplete was enabled # but we want to make sure expect(page).not_to have_selector('.atwho-view') - expect(request_count_before).to eq(request_count_after) end end context 'when editing a note' do it 'changes the text' do - find('.js-note-edit').trigger('click') + find('.js-note-edit').click page.within('.current-note-edit-form') do fill_in 'note[note]', with: 'new content' @@ -113,7 +110,7 @@ describe 'Comments on personal snippets', :js do open_more_actions_dropdown(snippet_notes[0]) page.within("#notes-list li#note_#{snippet_notes[0].id}") do - click_on 'Delete comment' + accept_confirm { click_on 'Delete comment' } end wait_for_requests diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index d732383a1e1..941765b7578 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -14,7 +14,7 @@ feature 'User creates snippet', :js do fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_description', with: 'My Snippet **Description**' page.within('.file-editor') do - find('.ace_editor').native.send_keys 'Hello World!' + find('.ace_text-input', visible: false).send_keys 'Hello World!' end end @@ -43,8 +43,8 @@ feature 'User creates snippet', :js do link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] expect(link).to match(%r{/uploads/-/system/temp/\h{32}/banana_sample\.gif\z}) - visit(link) - expect(page.status_code).to eq(200) + reqs = inspect_requests { visit(link) } + expect(reqs.first.status_code).to eq(200) end end @@ -61,8 +61,8 @@ feature 'User creates snippet', :js do link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) - visit(link) - expect(page.status_code).to eq(200) + reqs = inspect_requests { visit(link) } + expect(reqs.first.status_code).to eq(200) end scenario 'validation fails for the first time' do @@ -86,15 +86,15 @@ feature 'User creates snippet', :js do link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z}) - visit(link) - expect(page.status_code).to eq(200) + reqs = inspect_requests { visit(link) } + expect(reqs.first.status_code).to eq(200) end scenario 'Authenticated user creates a snippet with + in filename' do fill_in 'personal_snippet_title', with: 'My Snippet Title' page.within('.file-editor') do find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name' - find('.ace_editor').native.send_keys 'Hello World!' + find('.ace_text-input', visible: false).send_keys 'Hello World!' end click_button 'Create snippet' diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index 39d79a3327b..1f8bd8d681e 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -55,7 +55,7 @@ feature 'Master creates tag' do end end - scenario 'opens dropdown for ref', js: true do + scenario 'opens dropdown for ref', :js do click_link 'New tag' ref_row = find('.form-group:nth-of-type(2) .col-sm-10') page.within ref_row do @@ -63,7 +63,7 @@ feature 'Master creates tag' do expect(ref_input.value).to eq 'master' expect(find('.dropdown-toggle-text')).to have_content 'master' - find('.js-branch-select').trigger('click') + find('.js-branch-select').click expect(find('.dropdown-menu')).to have_content 'empty-branch' end diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index d6a6b8fc7d5..dfda664d673 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -10,7 +10,7 @@ feature 'Master deletes tag' do visit project_tags_path(project) end - context 'from the tags list page', js: true do + context 'from the tags list page', :js do scenario 'deletes the tag' do expect(page).to have_content 'v1.1.0' @@ -34,22 +34,37 @@ feature 'Master deletes tag' do end end - context 'when pre-receive hook fails', js: true do - before do - allow_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) - .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'Do not delete tags') + context 'when pre-receive hook fails', :js do + context 'when Gitaly operation_user_delete_tag feature is enabled' do + before do + allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag) + .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'Do not delete tags') + end + + scenario 'shows the error message' do + delete_first_tag + + expect(page).to have_content('Do not delete tags') + end end - scenario 'shows the error message' do - delete_first_tag + context 'when Gitaly operation_user_delete_tag feature is disabled', :skip_gitaly_mock do + before do + allow_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) + .and_raise(Gitlab::Git::HooksService::PreReceiveError, 'Do not delete tags') + end + + scenario 'shows the error message' do + delete_first_tag - expect(page).to have_content('Do not delete tags') + expect(page).to have_content('Do not delete tags') + end end end def delete_first_tag page.within('.content') do - first('.btn-remove').click + accept_confirm { first('.btn-remove').click } end end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index aeb0534b733..2dc3c5e3927 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' feature 'Task Lists' do include Warden::Test::Helpers - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:user2) { create(:user) } @@ -63,7 +63,7 @@ feature 'Task Lists' do end describe 'for Issues' do - describe 'multiple tasks', js: true do + describe 'multiple tasks', :js do let!(:issue) { create(:issue, description: markdown, author: user, project: project) } it 'renders' do @@ -103,7 +103,7 @@ feature 'Task Lists' do end end - describe 'single incomplete task', js: true do + describe 'single incomplete task', :js do let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) } it 'renders' do @@ -122,7 +122,7 @@ feature 'Task Lists' do end end - describe 'single complete task', js: true do + describe 'single complete task', :js do let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) } it 'renders' do @@ -141,7 +141,7 @@ feature 'Task Lists' do end end - describe 'nested tasks', js: true do + describe 'nested tasks', :js do let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } before do diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 47664de469a..bc472e74997 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Triggers', js: true do +feature 'Triggers', :js do let(:trigger_title) { 'trigger desc' } let(:user) { create(:user) } let(:user2) { create(:user) } @@ -45,7 +45,7 @@ feature 'Triggers', js: true do visit project_settings_ci_cd_path(@project) # See if edit page has correct descrption - find('a[title="Edit"]').click + find('a[title="Edit"]').send_keys(:return) expect(page.find('#trigger_description').value).to have_content 'trigger desc' end @@ -54,7 +54,7 @@ feature 'Triggers', js: true do visit project_settings_ci_cd_path(@project) # See if edit page opens, then fill in new description and save - find('a[title="Edit"]').click + find('a[title="Edit"]').send_keys(:return) fill_in 'trigger_description', with: new_trigger_title click_button 'Save trigger' @@ -70,7 +70,7 @@ feature 'Triggers', js: true do visit project_settings_ci_cd_path(@project) # See if the trigger can be edited and description is blank - find('a[title="Edit"]').click + find('a[title="Edit"]').send_keys(:return) expect(page.find('#trigger_description').value).to have_content '' # See if trigger can be updated with description and saved successfully @@ -94,12 +94,13 @@ feature 'Triggers', js: true do scenario 'take trigger ownership' do # See if "Take ownership" on trigger works post trigger creation - find('a.btn-trigger-take-ownership').click page.accept_confirm do - expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.' - expect(page.find('.triggers-list')).to have_content trigger_title - expect(page.find('.triggers-list .trigger-owner')).to have_content user.name + first(:link, "Take ownership").send_keys(:return) end + + expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.' + expect(page.find('.triggers-list')).to have_content trigger_title + expect(page.find('.triggers-list .trigger-owner')).to have_content user.name end end @@ -116,11 +117,12 @@ feature 'Triggers', js: true do scenario 'revoke trigger' do # See if "Revoke" on trigger works post trigger creation - find('a.btn-trigger-revoke').click page.accept_confirm do - expect(page.find('.flash-notice')).to have_content 'Trigger removed' - expect(page).to have_selector('p.settings-message.text-center.append-bottom-default') + find('a.btn-trigger-revoke').send_keys(:return) end + + expect(page.find('.flash-notice')).to have_content 'Trigger removed' + expect(page).to have_selector('p.settings-message.text-center.append-bottom-default') end end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index f3662cb184f..c9afef2a8de 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -79,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do first_u2f_device = register_u2f_device second_u2f_device = register_u2f_device(name: 'My other device') - click_on "Delete", match: :first + accept_confirm { click_on "Delete", match: :first } expect(page).to have_content('Successfully deleted') expect(page.body).not_to match(first_u2f_device.name) @@ -162,7 +162,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_css('.sign-out-link', visible: false) end end @@ -174,23 +173,10 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_css('.sign-out-link', visible: false) end end - it 'persists remember_me value via hidden field' do - gitlab_sign_in(user, remember: true) - - @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') - - within 'div#js-authenticate-u2f' do - field = first('input#user_remember_me', visible: false) - expect(field.value).to eq '1' - end - end - describe "when a given U2F device has already been registered by another user" do describe "but not the current user" do it "does not allow logging in with that particular device" do @@ -205,7 +191,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do # Try authenticating user with the old U2F device gitlab_sign_in(current_user) @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_content('Authentication via U2F device failed') end end @@ -223,7 +208,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do # Try authenticating user with the same U2F device gitlab_sign_in(current_user) @u2f_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_css('.sign-out-link', visible: false) end @@ -235,7 +219,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do unregistered_device = FakeU2fDevice.new(page, 'My device') gitlab_sign_in(user) unregistered_device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_content('Authentication via U2F device failed') end @@ -260,7 +243,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do [first_device, second_device].each do |device| gitlab_sign_in(user) device.respond_to_u2f_authentication - expect(page).to have_content('We heard back from your U2F device') expect(page).to have_css('.sign-out-link', visible: false) @@ -283,7 +265,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it "deletes u2f registrations" do visit profile_account_path - expect { click_on "Disable" }.to change { U2fRegistration.count }.by(-1) + expect do + accept_confirm { click_on "Disable" } + end.to change { U2fRegistration.count }.by(-1) end end end diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index e1c95590af1..972c10aaf23 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -14,43 +14,43 @@ feature 'User uploads file to note' do end context 'before uploading' do - it 'shows "Attach a file" button', js: true do + it 'shows "Attach a file" button', :js do expect(page).to have_button('Attach a file') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end end context 'uploading is in progress' do - it 'shows "Cancel" button on uploading', js: true do - dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + it 'cancels uploading on clicking to "Cancel" button', :js do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - expect(page).to have_button('Cancel') - end - - it 'cancels uploading on clicking to "Cancel" button', js: true do - dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - - click_button 'Cancel' + click_button 'Cancel' + end expect(page).to have_button('Attach a file') expect(page).not_to have_button('Cancel') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end - it 'shows "Attaching a file" message on uploading 1 file', js: true do - dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + it 'shows "Attaching a file" message on uploading 1 file', :js do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -') + expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -') + end end - it 'shows "Attaching 2 files" message on uploading 2 file', js: true do - dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4'), - Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + it 'shows "Attaching 2 files" message on uploading 2 file', :js do + slow_requests do + dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4'), + Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) - expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching 2 files -') + expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching 2 files -') + end end - it 'shows error message, "retry" and "attach a new file" link a if file is too big', js: true do + it 'shows error message, "retry" and "attach a new file" link a if file is too big', :js do dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4')], 0.01) error_text = 'File is too big (0.06MiB). Max filesize: 0.01MiB.' @@ -63,7 +63,7 @@ feature 'User uploads file to note' do end context 'uploading is complete' do - it 'shows "Attach a file" button on uploading complete', js: true do + it 'shows "Attach a file" button on uploading complete', :js do dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')]) wait_for_requests @@ -71,7 +71,7 @@ feature 'User uploads file to note' do expect(page).not_to have_selector('.uploading-progress-container', visible: true) end - scenario 'they see the attached file', js: true do + scenario 'they see the attached file', :js do dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')]) click_button 'Comment' wait_for_requests diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 13760b4c2fc..8c697e33436 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Snippets tab on a user profile', js: true do +describe 'Snippets tab on a user profile', :js do context 'when the user has snippets' do let(:user) { create(:user) } diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 15b89dac572..a9973cdf214 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Users', js: true do +feature 'Users', :js do let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } scenario 'GET /users/sign_in creates a new user account' do @@ -24,6 +24,7 @@ feature 'Users', js: true do user.reload expect(user.reset_password_token).not_to be_nil + find('a[href="#login-pane"]').click gitlab_sign_in(user) expect(current_path).to eq root_path diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb index 6794bf4f4ba..c78f7d0d9be 100644 --- a/spec/features/variables_spec.rb +++ b/spec/features/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Project variables', js: true do +describe 'Project variables', :js do let(:user) { create(:user) } let(:project) { create(:project) } let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') } @@ -82,7 +82,7 @@ describe 'Project variables', js: true do it 'deletes variable' do page.within('.variables-table') do - click_on 'Remove' + accept_confirm { click_on 'Remove' } end expect(page).not_to have_selector('variables-table') diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 91f34973ba5..9e3f2c69606 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -46,6 +46,15 @@ describe BranchesFinder do expect(result.count).to eq(1) end + it 'filters branches by name ignoring letter case' do + branches_finder = described_class.new(repository, { search: 'FiX' }) + + result = branches_finder.execute + + expect(result.first.name).to eq('fix') + expect(result.count).to eq(1) + end + it 'does not find any branch with that name' do branches_finder = described_class.new(repository, { search: 'random' }) diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb new file mode 100644 index 00000000000..074914420a1 --- /dev/null +++ b/spec/finders/group_descendants_finder_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +describe GroupDescendantsFinder do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:params) { {} } + subject(:finder) do + described_class.new(current_user: user, parent_group: group, params: params) + end + + before do + group.add_owner(user) + end + + describe '#has_children?' do + it 'is true when there are projects' do + create(:project, namespace: group) + + expect(finder.has_children?).to be_truthy + end + + context 'when there are subgroups', :nested_groups do + it 'is true when there are projects' do + create(:group, parent: group) + + expect(finder.has_children?).to be_truthy + end + end + end + + describe '#execute' do + it 'includes projects' do + project = create(:project, namespace: group) + + expect(finder.execute).to contain_exactly(project) + end + + context 'when archived is `true`' do + let(:params) { { archived: 'true' } } + + it 'includes archived projects' do + archived_project = create(:project, namespace: group, archived: true) + project = create(:project, namespace: group) + + expect(finder.execute).to contain_exactly(archived_project, project) + end + end + + context 'when archived is `only`' do + let(:params) { { archived: 'only' } } + + it 'includes only archived projects' do + archived_project = create(:project, namespace: group, archived: true) + _project = create(:project, namespace: group) + + expect(finder.execute).to contain_exactly(archived_project) + end + end + + it 'does not include archived projects' do + _archived_project = create(:project, :archived, namespace: group) + + expect(finder.execute).to be_empty + end + + context 'with a filter' do + let(:params) { { filter: 'test' } } + + it 'includes only projects matching the filter' do + _other_project = create(:project, namespace: group) + matching_project = create(:project, namespace: group, name: 'testproject') + + expect(finder.execute).to contain_exactly(matching_project) + end + end + end + + context 'with nested groups', :nested_groups do + let!(:project) { create(:project, namespace: group) } + let!(:subgroup) { create(:group, :private, parent: group) } + + describe '#execute' do + it 'contains projects and subgroups' do + expect(finder.execute).to contain_exactly(subgroup, project) + end + + it 'does not include subgroups the user does not have access to' do + subgroup.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + public_subgroup = create(:group, :public, parent: group, path: 'public-group') + other_subgroup = create(:group, :private, parent: group, path: 'visible-private-group') + other_user = create(:user) + other_subgroup.add_developer(other_user) + + finder = described_class.new(current_user: other_user, parent_group: group) + + expect(finder.execute).to contain_exactly(public_subgroup, other_subgroup) + end + + it 'only includes public groups when no user is given' do + public_subgroup = create(:group, :public, parent: group) + _private_subgroup = create(:group, :private, parent: group) + + finder = described_class.new(current_user: nil, parent_group: group) + + expect(finder.execute).to contain_exactly(public_subgroup) + end + + context 'when archived is `true`' do + let(:params) { { archived: 'true' } } + + it 'includes archived projects in the count of subgroups' do + create(:project, namespace: subgroup, archived: true) + + expect(finder.execute.first.preloaded_project_count).to eq(1) + end + end + + context 'with a filter' do + let(:params) { { filter: 'test' } } + + it 'contains only matching projects and subgroups' do + matching_project = create(:project, namespace: group, name: 'Testproject') + matching_subgroup = create(:group, name: 'testgroup', parent: group) + + expect(finder.execute).to contain_exactly(matching_subgroup, matching_project) + end + + it 'does not include subgroups the user does not have access to' do + _invisible_subgroup = create(:group, :private, parent: group, name: 'test1') + other_subgroup = create(:group, :private, parent: group, name: 'test2') + public_subgroup = create(:group, :public, parent: group, name: 'test3') + other_subsubgroup = create(:group, :private, parent: other_subgroup, name: 'test4') + other_user = create(:user) + other_subgroup.add_developer(other_user) + + finder = described_class.new(current_user: other_user, + parent_group: group, + params: params) + + expect(finder.execute).to contain_exactly(other_subgroup, public_subgroup, other_subsubgroup) + end + + context 'with matching children' do + it 'includes a group that has a subgroup matching the query and its parent' do + matching_subgroup = create(:group, :private, name: 'testgroup', parent: subgroup) + + expect(finder.execute).to contain_exactly(subgroup, matching_subgroup) + end + + it 'includes the parent of a matching project' do + matching_project = create(:project, namespace: subgroup, name: 'Testproject') + + expect(finder.execute).to contain_exactly(subgroup, matching_project) + end + + it 'does not include the parent itself' do + group.update!(name: 'test') + + expect(finder.execute).not_to include(group) + end + end + end + end + end +end diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb new file mode 100644 index 00000000000..c81bfd7932c --- /dev/null +++ b/spec/finders/merge_request_target_project_finder_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe MergeRequestTargetProjectFinder do + include ProjectForksHelper + + let(:user) { create(:user) } + subject(:finder) { described_class.new(current_user: user, source_project: forked_project) } + + shared_examples 'finding related projects' do + it 'finds sibling projects and base project' do + other_fork + + expect(finder.execute).to contain_exactly(base_project, other_fork, forked_project) + end + + it 'does not include projects that have merge requests turned off' do + other_fork.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + base_project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + + expect(finder.execute).to contain_exactly(forked_project) + end + end + + context 'public projects' do + let(:base_project) { create(:project, :public, path: 'base') } + let(:forked_project) { fork_project(base_project) } + let(:other_fork) { fork_project(base_project) } + + it_behaves_like 'finding related projects' + end + + context 'private projects' do + let(:base_project) { create(:project, :private, path: 'base') } + let(:forked_project) { fork_project(base_project, base_project.owner) } + let(:other_fork) { fork_project(base_project, base_project.owner) } + + context 'when the user is a member of all projects' do + before do + base_project.add_developer(user) + forked_project.add_developer(user) + other_fork.add_developer(user) + end + + it_behaves_like 'finding related projects' + end + + it 'only finds the projects the user is a member of' do + other_fork.add_developer(user) + base_project.add_developer(user) + + expect(finder.execute).to contain_exactly(other_fork, base_project) + end + end +end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 95f445e7905..883bdf3746a 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -1,12 +1,18 @@ require 'spec_helper' describe MergeRequestsFinder do + include ProjectForksHelper + let(:user) { create :user } let(:user2) { create :user } - let(:project1) { create(:project) } - let(:project2) { create(:project, forked_from_project: project1) } - let(:project3) { create(:project, :archived, forked_from_project: project1) } + let(:project1) { create(:project, :public) } + let(:project2) { fork_project(project1, user) } + let(:project3) do + p = fork_project(project1, user) + p.update!(archived: true) + p + end let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb index 1bab6d64388..4249c52c481 100644 --- a/spec/finders/users_finder_spec.rb +++ b/spec/finders/users_finder_spec.rb @@ -56,6 +56,15 @@ describe UsersFinder do expect(users.map(&:username)).not_to include([filtered_user_before.username, filtered_user_after.username]) end + + it 'does not filter by custom attributes' do + users = described_class.new( + user, + custom_attributes: { foo: 'bar' } + ).execute + + expect(users).to contain_exactly(user, user1, user2, omniauth_user) + end end context 'with an admin user' do @@ -72,6 +81,19 @@ describe UsersFinder do expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user) end + + it 'filters by custom attributes' do + create :user_custom_attribute, user: user1, key: 'foo', value: 'foo' + create :user_custom_attribute, user: user1, key: 'bar', value: 'bar' + create :user_custom_attribute, user: user2, key: 'foo', value: 'foo' + + users = described_class.new( + admin, + custom_attributes: { foo: 'foo', bar: 'bar' } + ).execute + + expect(users).to contain_exactly(user1) + end end end end diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json new file mode 100644 index 00000000000..1f255a17881 --- /dev/null +++ b/spec/fixtures/api/schemas/cluster_status.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "required" : [ + "status" + ], + "properties" : { + "status": { "type": "string" }, + "status_reason": { "type": ["string", "null"] } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json new file mode 100644 index 00000000000..3d3329a3406 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/issue.json @@ -0,0 +1,44 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "author_id": { "type": "integer" }, + "description": { "type": ["string", "null"] }, + "lock_version": { "type": ["string", "null"] }, + "milestone_id": { "type": ["string", "null"] }, + "title": { "type": "string" }, + "moved_to_id": { "type": ["integer", "null"] }, + "project_id": { "type": "integer" }, + "web_url": { "type": "string" }, + "state": { "type": "string" }, + "create_note_path": { "type": "string" }, + "preview_note_path": { "type": "string" }, + "current_user": { + "type": "object", + "properties": { + "can_create_note": { "type": "boolean" }, + "can_update": { "type": "boolean" } + } + }, + "created_at": { "type": "date-time" }, + "updated_at": { "type": "date-time" }, + "branch_name": { "type": ["string", "null"] }, + "due_date": { "type": "date" }, + "confidential": { "type": "boolean" }, + "discussion_locked": { "type": ["boolean", "null"] }, + "updated_by_id": { "type": ["string", "null"] }, + "deleted_at": { "type": ["string", "null"] }, + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["integer", "null"] }, + "human_total_time_spent": { "type": ["integer", "null"] }, + "milestone": { "type": ["object", "null"] }, + "labels": { + "type": "array", + "items": { "$ref": "label.json" } + }, + "assignees": { "type": ["array", "null"] } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar.json b/spec/fixtures/api/schemas/entities/issue_sidebar.json new file mode 100644 index 00000000000..682e345d5f5 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/issue_sidebar.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "properties" : { + "id": { "type": "integer" }, + "iid": { "type": "integer" }, + "subscribed": { "type": "boolean" }, + "time_estimate": { "type": "integer" }, + "total_time_spent": { "type": "integer" }, + "human_time_estimate": { "type": ["integer", "null"] }, + "human_total_time_spent": { "type": ["integer", "null"] }, + "participants": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.json" } + }, + "assignees": { + "type": "array", + "items": { "$ref": "../public_api/v4/user/basic.json" } + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/label.json b/spec/fixtures/api/schemas/entities/label.json new file mode 100644 index 00000000000..40dff764c17 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/label.json @@ -0,0 +1,26 @@ +{ + "type": "object", + "required": [ + "id", + "color", + "description", + "title", + "priority" + ], + "properties": { + "id": { "type": "integer" }, + "color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{3}{1,2}$" + }, + "description": { "type": ["string", "null"] }, + "text_color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{3}{1,2}$" + }, + "type": { "type": "string" }, + "title": { "type": "string" }, + "priority": { "type": ["integer", "null"] } + }, + "additionalProperties": false +}
\ No newline at end of file diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json index 1030f323a1f..ba094ba1657 100644 --- a/spec/fixtures/api/schemas/entities/merge_request.json +++ b/spec/fixtures/api/schemas/entities/merge_request.json @@ -46,6 +46,7 @@ "branch_missing": { "type": "boolean" }, "has_conflicts": { "type": "boolean" }, "can_be_merged": { "type": "boolean" }, + "mergeable": { "type": "boolean" }, "project_archived": { "type": "boolean" }, "only_allow_merge_if_pipeline_succeeds": { "type": "boolean" }, "has_ci": { "type": "boolean" }, @@ -93,10 +94,12 @@ "merge_commit_message_with_description": { "type": "string" }, "diverged_commits_count": { "type": "integer" }, "commit_change_content_path": { "type": "string" }, - "remove_wip_path": { "type": "string" }, + "remove_wip_path": { "type": ["string", "null"] }, "commits_count": { "type": "integer" }, "remove_source_branch": { "type": ["boolean", "null"] }, - "merge_ongoing": { "type": "boolean" } + "merge_ongoing": { "type": "boolean" }, + "ff_only_enabled": { "type": ["boolean", false] }, + "should_be_rebased": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json index 6b14188582a..995f13381ad 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_basic.json +++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json @@ -9,7 +9,9 @@ "human_time_estimate": { "type": ["string", "null"] }, "human_total_time_spent": { "type": ["string", "null"] }, "merge_error": { "type": ["string", "null"] }, - "assignee_id": { "type": ["integer", "null"] } + "assignee_id": { "type": ["integer", "null"] }, + "subscribed": { "type": ["boolean", "null"] }, + "participants": { "type": "array" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json index e1f62508933..a55ecaa5697 100644 --- a/spec/fixtures/api/schemas/issue.json +++ b/spec/fixtures/api/schemas/issue.json @@ -19,32 +19,7 @@ }, "labels": { "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "color", - "description", - "title", - "priority" - ], - "properties": { - "id": { "type": "integer" }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{3}{1,2}+$" - }, - "description": { "type": ["string", "null"] }, - "text_color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{3}{1,2}+$" - }, - "type": { "type": "string" }, - "title": { "type": "string" }, - "priority": { "type": ["integer", "null"] } - }, - "additionalProperties": false - } + "items": { "$ref": "entities/label.json" } }, "assignee": { "id": { "type": "integet" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json index b7b2535c204..88a3cad62f6 100644 --- a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json +++ b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json @@ -5,11 +5,18 @@ { "required" : [ "stats", - "status" + "status", + "last_pipeline" ], "properties": { "stats": { "$ref": "../commit_stats.json" }, - "status": { "type": ["string", "null"] } + "status": { "type": ["string", "null"] }, + "last_pipeline": { + "oneOf": [ + { "type": "null" }, + { "$ref": "../pipeline/basic.json" } + ] + } } } ] diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json index 03c422ab023..5c08dbc3b96 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issues.json +++ b/spec/fixtures/api/schemas/public_api/v4/issues.json @@ -9,6 +9,7 @@ "title": { "type": "string" }, "description": { "type": ["string", "null"] }, "state": { "type": "string" }, + "discussion_locked": { "type": ["boolean", "null"] }, "closed_at": { "type": "date" }, "created_at": { "type": "date" }, "updated_at": { "type": "date" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json index 31b3f4ba946..5828be5255b 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json @@ -72,6 +72,7 @@ "user_notes_count": { "type": "integer" }, "should_remove_source_branch": { "type": ["boolean", "null"] }, "force_remove_source_branch": { "type": ["boolean", "null"] }, + "discussion_locked": { "type": ["boolean", "null"] }, "web_url": { "type": "uri" }, "time_stats": { "time_estimate": { "type": "integer" }, diff --git a/spec/fixtures/api/schemas/public_api/v4/pages_domains.json b/spec/fixtures/api/schemas/public_api/v4/pages_domains.json new file mode 100644 index 00000000000..0de1d0f1228 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/pages_domains.json @@ -0,0 +1,23 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties": { + "domain": { "type": "string" }, + "url": { "type": "uri" }, + "certificate": { + "type": "object", + "properties": { + "subject": { "type": "string" }, + "expired": { "type": "boolean" }, + "certificate": { "type": "string" }, + "certificate_text": { "type": "string" } + }, + "required": ["subject", "expired"], + "additionalProperties": false + } + }, + "required": ["domain", "url"], + "additionalProperties": false + } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json new file mode 100644 index 00000000000..0d127dc5297 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "required" : [ + "id", + "sha", + "ref", + "status" + ], + "properties" : { + "id": { "type": "integer" }, + "sha": { "type": "string" }, + "ref": { "type": "string" }, + "status": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json index e6c1d9c9d84..aa066883c47 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/login.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json @@ -27,11 +27,9 @@ "can_create_group", "can_create_project", "two_factor_enabled", - "external", - "private_token" + "external" ], "properties": { - "$ref": "full.json", - "private_token": { "type": "string" } + "$ref": "full.json" } } diff --git a/spec/fixtures/api/schemas/registry/repositories.json b/spec/fixtures/api/schemas/registry/repositories.json new file mode 100644 index 00000000000..4978bd89cda --- /dev/null +++ b/spec/fixtures/api/schemas/registry/repositories.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "repository.json" + } +} diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json new file mode 100644 index 00000000000..4175642eb00 --- /dev/null +++ b/spec/fixtures/api/schemas/registry/repository.json @@ -0,0 +1,27 @@ +{ + "type": "object", + "required" : [ + "id", + "path", + "location", + "tags_path" + ], + "properties" : { + "id": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags_path": { + "type": "string" + }, + "destroy_path": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/registry/tag.json b/spec/fixtures/api/schemas/registry/tag.json new file mode 100644 index 00000000000..3a2c88791e1 --- /dev/null +++ b/spec/fixtures/api/schemas/registry/tag.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "required" : [ + "name", + "location" + ], + "properties" : { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "short_revision": { + "type": "string", + "minLength": 9, + "maxLength": 9 + }, + "total_size": { + "type": "integer" + }, + "created_at": { + "type": "date" + }, + "destroy_path": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/registry/tags.json b/spec/fixtures/api/schemas/registry/tags.json new file mode 100644 index 00000000000..c72f957459a --- /dev/null +++ b/spec/fixtures/api/schemas/registry/tags.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "tag.json" + } +} diff --git a/spec/fixtures/config/kubeconfig.yml b/spec/fixtures/config/kubeconfig.yml index c4e8e573c32..5152dae0104 100644 --- a/spec/fixtures/config/kubeconfig.yml +++ b/spec/fixtures/config/kubeconfig.yml @@ -4,7 +4,7 @@ clusters: - name: gitlab-deploy cluster: server: https://kube.domain.com - certificate-authority-data: "UEVN\n" + certificate-authority-data: "UEVN" contexts: - name: gitlab-deploy context: diff --git a/spec/fixtures/pages.tar.gz b/spec/fixtures/pages.tar.gz Binary files differindex d0e89378b3e..5c4ea9690e8 100644 --- a/spec/fixtures/pages.tar.gz +++ b/spec/fixtures/pages.tar.gz diff --git a/spec/fixtures/pages.zip b/spec/fixtures/pages.zip Binary files differindex 9558fcd4b94..9bb75f953f8 100644 --- a/spec/fixtures/pages.zip +++ b/spec/fixtures/pages.zip diff --git a/spec/fixtures/ssh_host_example_key.pub b/spec/fixtures/ssh_host_example_key.pub new file mode 100644 index 00000000000..6bac42b3ad0 --- /dev/null +++ b/spec/fixtures/ssh_host_example_key.pub @@ -0,0 +1 @@ +random content diff --git a/spec/fixtures/trace/trace_with_sections b/spec/fixtures/trace/trace_with_sections new file mode 100644 index 00000000000..21dff3928c3 --- /dev/null +++ b/spec/fixtures/trace/trace_with_sections @@ -0,0 +1,15 @@ +[0KRunning with gitlab-runner dev (HEAD) + on kitsune minikube (a21b584f) +[0;m[0;33mWARNING: Namespace is empty, therefore assuming 'default'. +[0;m[0KUsing Kubernetes namespace: default +[0;m[0KUsing Kubernetes executor with image alpine:3.4 ... +[0;msection_start:1506004954:prepare_script
[0KWaiting for pod default/runner-a21b584f-project-1208199-concurrent-0sg03f to be running, status is Pending +Running on runner-a21b584f-project-1208199-concurrent-0sg03f via kitsune.local... +section_end:1506004957:prepare_script
[0Ksection_start:1506004957:get_sources
[0K[32;1mCloning repository...[0;m +Cloning into '/nolith/ci-tests'... +[32;1mChecking out dddd7a6e as master...[0;m +[32;1mSkipping Git submodules setup[0;m +section_end:1506004958:get_sources
[0Ksection_start:1506004958:restore_cache
[0Ksection_end:1506004958:restore_cache
[0Ksection_start:1506004958:download_artifacts
[0Ksection_end:1506004958:download_artifacts
[0Ksection_start:1506004958:build_script
[0K[32;1m$ whoami[0;m +root +section_end:1506004959:build_script
[0Ksection_start:1506004959:after_script
[0Ksection_end:1506004959:after_script
[0Ksection_start:1506004959:archive_cache
[0Ksection_end:1506004959:archive_cache
[0Ksection_start:1506004959:upload_artifacts
[0Ksection_end:1506004959:upload_artifacts
[0K[32;1mJob succeeded +[0;m diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 10bc5f2ecd2..7a241b02d28 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -57,15 +57,17 @@ describe ApplicationHelper do end describe 'project_icon' do + let(:asset_host) { 'http://assets' } + it 'returns an url for the avatar' do - project = create(:project, avatar: File.open(uploaded_image_temp_path)) + project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" - allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) - avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon(project.full_path).to_s) .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" @@ -307,4 +309,12 @@ describe ApplicationHelper do end end end + + describe '#locale_path' do + it 'returns the locale path with an `_`' do + Gitlab::I18n.with_locale('pt-BR') do + expect(helper.locale_path).to include('assets/locale/pt_BR/app') + end + end + end end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index 6a3945c0ebc..bc2422aba90 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -8,17 +8,13 @@ describe CiStatusHelper do describe '#ci_icon_for_status' do it 'renders to correct svg on success' do - expect(helper).to receive(:render) - .with('shared/icons/icon_status_success.svg', anything) - - helper.ci_icon_for_status(success_commit.status) + expect(helper.ci_icon_for_status('success').to_s) + .to include 'status_success' end it 'renders the correct svg on failure' do - expect(helper).to receive(:render) - .with('shared/icons/icon_status_failed.svg', anything) - - helper.ci_icon_for_status(failed_commit.status) + expect(helper.ci_icon_for_status('failed').to_s) + .to include 'status_failed' end end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index d5536fcb22b..8a80b88da5d 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -1,96 +1,6 @@ require 'spec_helper' describe EventsHelper do - describe '#event_note' do - let(:user) { build(:user) } - - before do - allow(helper).to receive(:current_user).and_return(user) - end - - it 'displays one line of plain text without alteration' do - input = 'A short, plain note' - expect(helper.event_note(input)).to match(input) - expect(helper.event_note(input)).not_to match(/\.\.\.\z/) - end - - it 'displays inline code' do - input = 'A note with `inline code`' - expected = 'A note with <code>inline code</code>' - - expect(helper.event_note(input)).to match(expected) - end - - it 'truncates a note with multiple paragraphs' do - input = "Paragraph 1\n\nParagraph 2" - expected = 'Paragraph 1...' - - expect(helper.event_note(input)).to match(expected) - end - - it 'displays the first line of a code block' do - input = "```\nCode block\nwith two lines\n```" - expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>} - - expect(helper.event_note(input)).to match(expected) - end - - it 'truncates a single long line of text' do - text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars - input = text * 4 - expected = (text * 2).sub(/.{3}/, '...') - - expect(helper.event_note(input)).to match(expected) - end - - it 'preserves a link href when link text is truncated' do - text = 'The quick brown fox jumped over the lazy dog' # 44 chars - input = "#{text}#{text}#{text} " # 133 chars - link_url = 'http://example.com/foo/bar/baz' # 30 chars - input << link_url - expected_link_text = 'http://example...</a>' - - expect(helper.event_note(input)).to match(link_url) - expect(helper.event_note(input)).to match(expected_link_text) - end - - it 'preserves code color scheme' do - input = "```ruby\ndef test\n 'hello world'\nend\n```" - expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \ - "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ - "</code></pre>" - expect(helper.event_note(input)).to eq(expected) - end - - it 'preserves data-src for lazy images' do - input = "![ImageTest](/uploads/test.png)" - image_url = "data-src=\"/uploads/test.png\"" - expect(helper.event_note(input)).to match(image_url) - end - - context 'labels formatting' do - let(:input) { 'this should be ~label_1' } - - def format_event_note(project) - create(:label, title: 'label_1', project: project) - - helper.event_note(input, { project: project }) - end - - it 'preserves style attribute for a label that can be accessed by current_user' do - project = create(:project, :public) - - expect(format_event_note(project)).to match(/span class=.*style=.*/) - end - - it 'does not style a label that can not be accessed by current_user' do - project = create(:project, :private) - - expect(format_event_note(project)).to eq("<p>#{input}</p>") - end - end - end - describe '#event_commit_title' do let(:message) { "foo & bar " + "A" * 70 + "\n" + "B" * 80 } subject { helper.event_commit_title(message) } diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index a44b200c5da..6c4f7050ee0 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -63,4 +63,30 @@ describe GitlabRoutingHelper do it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) } end end + + describe '#preview_markdown_path' do + let(:project) { create(:project) } + + it 'returns group preview markdown path for a group parent' do + group = create(:group) + + expect(preview_markdown_path(group)).to eq("/groups/#{group.path}/preview_markdown") + end + + it 'returns project preview markdown path for a project parent' do + expect(preview_markdown_path(project)).to eq("/#{project.full_path}/preview_markdown") + end + + it 'returns snippet preview markdown path for a personal snippet' do + @snippet = create(:personal_snippet) + + expect(preview_markdown_path(nil)).to eq("/snippets/preview_markdown") + end + + it 'returns project preview markdown path for a project snippet' do + @snippet = create(:project_snippet, project: project) + + expect(preview_markdown_path(project)).to eq("/#{project.full_path}/preview_markdown") + end + end end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 36031ac1a28..97f0ed4904e 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper + let(:asset_host) { 'http://assets' } + describe 'group_icon' do avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') @@ -10,14 +12,53 @@ describe GroupsHelper do group = create(:group) group.avatar = fixture_file_upload(avatar_file_path) group.save! - expect(group_icon(group.path).to_s) + + avatar_url = "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + avatar_url = "#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif" + + expect(helper.group_icon(group).to_s) + .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + end + end + + describe 'group_icon_url' do + avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + + it 'returns an url for the avatar' do + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an CDN url for the avatar' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) + .to match("#{asset_host}/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") + end + + it 'returns an based url for the avatar if private' do + allow(ActionController::Base).to receive(:asset_host).and_return(asset_host) + group = create(:group, :private) + group.avatar = fixture_file_upload(avatar_file_path) + group.save! + expect(group_icon_url(group.path).to_s) .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif") end it 'gives default avatar_icon when no avatar is present' do group = create(:group) group.save! - expect(group_icon(group.path)).to match('group_avatar.png') + expect(group_icon_url(group.path)).to match_asset_path('group_avatar.png') end end diff --git a/spec/helpers/instance_configuration_helper_spec.rb b/spec/helpers/instance_configuration_helper_spec.rb new file mode 100644 index 00000000000..5d716b9191d --- /dev/null +++ b/spec/helpers/instance_configuration_helper_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe InstanceConfigurationHelper do + describe '#instance_configuration_cell_html' do + describe 'if not block is passed' do + it 'returns the parameter if present' do + expect(helper.instance_configuration_cell_html('gitlab')).to eq('gitlab') + end + + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_cell_html(nil)).to eq('-') + expect(helper.instance_configuration_cell_html('')).to eq('-') + end + end + + describe 'if a block is passed' do + let(:upcase_block) { ->(value) { value.upcase } } + + it 'returns the result of the block' do + expect(helper.instance_configuration_cell_html('gitlab', &upcase_block)).to eq('GITLAB') + expect(helper.instance_configuration_cell_html('gitlab') { |v| v.upcase }).to eq('GITLAB') + end + + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_cell_html(nil, &upcase_block)).to eq('-') + expect(helper.instance_configuration_cell_html(nil) { |v| v.upcase }).to eq('-') + expect(helper.instance_configuration_cell_html('', &upcase_block)).to eq('-') + end + end + + it 'boolean are valid values to display' do + expect(helper.instance_configuration_cell_html(true)).to eq(true) + expect(helper.instance_configuration_cell_html(false)).to eq(false) + end + end + + describe '#instance_configuration_human_size_cell' do + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_human_size_cell(nil)).to eq('-') + expect(helper.instance_configuration_human_size_cell('')).to eq('-') + end + + it 'accepts the value in bytes' do + expect(helper.instance_configuration_human_size_cell(1024)).to eq('1 KB') + end + + it 'returns the value in human size readable format' do + expect(helper.instance_configuration_human_size_cell(1048576)).to eq('1 MB') + end + end +end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index ead3e28438e..cb851d828f2 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -159,4 +159,36 @@ describe IssuablesHelper do end end end + + describe '#issuable_initial_data' do + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + allow(helper).to receive(:can?).and_return(true) + end + + it 'returns the correct json for an issue' do + issue = create(:issue, author: user, description: 'issue text') + @project = issue.project + + expected_data = { + 'endpoint' => "/#{@project.full_path}/issues/#{issue.iid}", + 'canUpdate' => true, + 'canDestroy' => true, + 'issuableRef' => "##{issue.iid}", + 'markdownPreviewPath' => "/#{@project.full_path}/preview_markdown", + 'markdownDocsPath' => '/help/user/markdown', + 'issuableTemplates' => [], + 'projectPath' => @project.path, + 'projectNamespace' => @project.namespace.path, + 'initialTitleHtml' => issue.title, + 'initialTitleText' => issue.title, + 'initialDescriptionHtml' => '<p dir="auto">issue text</p>', + 'initialDescriptionText' => 'issue text', + 'initialTaskStatus' => '0 of 0 tasks completed' + } + expect(JSON.parse(helper.issuable_initial_data(issue))).to eq(expected_data) + end + end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 03d706062b7..62ea6d48542 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -67,7 +67,7 @@ describe MarkupHelper do describe 'without redacted attribute' do it 'renders the markdown value' do - expect(Banzai).to receive(:render_field).with(commit, attribute).and_call_original + expect(Banzai).to receive(:render_field).with(commit, attribute, {}).and_call_original helper.markdown_field(commit, attribute) end @@ -252,38 +252,141 @@ describe MarkupHelper do end describe '#first_line_in_markdown' do - it 'truncates Markdown properly' do - text = "@#{user.username}, can you look at this?\nHello world\n" - actual = first_line_in_markdown(text, 100, project: project) + shared_examples_for 'common markdown examples' do + let(:project_base) { build(:project, :repository) } - doc = Nokogiri::HTML.parse(actual) + it 'displays inline code' do + object = create_object('Text with `inline code`') + expected = 'Text with <code>inline code</code>' - # Make sure we didn't create invalid markup - expect(doc.errors).to be_empty + expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) + end - # Leading user link - expect(doc.css('a').length).to eq(1) - expect(doc.css('a')[0].attr('href')).to eq user_path(user) - expect(doc.css('a')[0].text).to eq "@#{user.username}" + it 'truncates the text with multiple paragraphs' do + object = create_object("Paragraph 1\n\nParagraph 2") + expected = 'Paragraph 1...' - expect(doc.content).to eq "@#{user.username}, can you look at this?..." - end + expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) + end - it 'truncates Markdown with emoji properly' do - text = "foo :wink:\nbar :grinning:" - actual = first_line_in_markdown(text, 100, project: project) + it 'displays the first line of a code block' do + object = create_object("```\nCode block\nwith two lines\n```") + expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>} - doc = Nokogiri::HTML.parse(actual) + expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) + end - # Make sure we didn't create invalid markup - # But also account for the 2 errors caused by the unknown `gl-emoji` elements - expect(doc.errors.length).to eq(2) + it 'truncates a single long line of text' do + text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars + object = create_object(text * 4) + expected = (text * 2).sub(/.{3}/, '...') + + expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected) + end + + it 'preserves a link href when link text is truncated' do + text = 'The quick brown fox jumped over the lazy dog' # 44 chars + input = "#{text}#{text}#{text} " # 133 chars + link_url = 'http://example.com/foo/bar/baz' # 30 chars + input << link_url + object = create_object(input) + expected_link_text = 'http://example...</a>' + + expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(link_url) + expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected_link_text) + end + + it 'preserves code color scheme' do + object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") + expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \ + "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ + "</code></pre>" + + expect(first_line_in_markdown(object, attribute, 150, project: project)).to eq(expected) + end + + it 'preserves data-src for lazy images' do + object = create_object("![ImageTest](/uploads/test.png)") + image_url = "data-src=\".*/uploads/test.png\"" + + expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(image_url) + end + + context 'labels formatting' do + let(:label_title) { 'this should be ~label_1' } + + def create_and_format_label(project) + create(:label, title: 'label_1', project: project) + object = create_object(label_title, project: project) - expect(doc.css('gl-emoji').length).to eq(2) - expect(doc.css('gl-emoji')[0].attr('data-name')).to eq 'wink' - expect(doc.css('gl-emoji')[1].attr('data-name')).to eq 'grinning' + first_line_in_markdown(object, attribute, 150, project: project) + end - expect(doc.content).to eq "foo 😉\nbar 😀" + it 'preserves style attribute for a label that can be accessed by current_user' do + project = create(:project, :public) + + expect(create_and_format_label(project)).to match(/span class=.*style=.*/) + end + + it 'does not style a label that can not be accessed by current_user' do + project = create(:project, :private) + + expect(create_and_format_label(project)).to eq("<p>#{label_title}</p>") + end + end + + it 'truncates Markdown properly' do + object = create_object("@#{user.username}, can you look at this?\nHello world\n") + actual = first_line_in_markdown(object, attribute, 100, project: project) + + doc = Nokogiri::HTML.parse(actual) + + # Make sure we didn't create invalid markup + expect(doc.errors).to be_empty + + # Leading user link + expect(doc.css('a').length).to eq(1) + expect(doc.css('a')[0].attr('href')).to eq user_path(user) + expect(doc.css('a')[0].text).to eq "@#{user.username}" + + expect(doc.content).to eq "@#{user.username}, can you look at this?..." + end + + it 'truncates Markdown with emoji properly' do + object = create_object("foo :wink:\nbar :grinning:") + actual = first_line_in_markdown(object, attribute, 100, project: project) + + doc = Nokogiri::HTML.parse(actual) + + # Make sure we didn't create invalid markup + # But also account for the 2 errors caused by the unknown `gl-emoji` elements + expect(doc.errors.length).to eq(2) + + expect(doc.css('gl-emoji').length).to eq(2) + expect(doc.css('gl-emoji')[0].attr('data-name')).to eq 'wink' + expect(doc.css('gl-emoji')[1].attr('data-name')).to eq 'grinning' + + expect(doc.content).to eq "foo 😉\nbar 😀" + end + end + + context 'when the asked attribute can be redacted' do + include_examples 'common markdown examples' do + let(:attribute) { :note } + def create_object(title, project: project_base) + build(:note, note: title, project: project) + end + end + end + + context 'when the asked attribute can not be redacted' do + include_examples 'common markdown examples' do + let(:attribute) { :body } + def create_object(title, project: project_base) + issue = build(:issue, title: title) + build(:todo, :done, project: project_base, author: user, target: issue) + end + end end end diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 7d1c17909bf..fd7900c32f4 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe MergeRequestsHelper do + include ProjectForksHelper describe 'ci_build_details_path' do let(:project) { create(:project) } let(:merge_request) { MergeRequest.new } @@ -31,10 +32,10 @@ describe MergeRequestsHelper do describe 'within different projects' do let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } - let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } + let(:forked_project) { fork_project(project) } + let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) } subject { format_mr_branch_names(merge_request) } - let(:source_title) { "#{fork_project.full_path}:#{merge_request.source_branch}" } + let(:source_title) { "#{forked_project.full_path}:#{merge_request.source_branch}" } let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" } it { is_expected.to eq([source_title, target_title]) } diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 9aca3987657..baf927a9acc 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -54,7 +54,7 @@ describe PageLayoutHelper do describe 'page_image' do it 'defaults to the GitLab logo' do - expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png' end %w(project user group).each do |type| @@ -70,13 +70,13 @@ describe PageLayoutHelper do object = double(avatar_url: nil) assign(type, object) - expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png' end end context "with no assignments" do it 'falls back to the default' do - expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png' end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 7ded95d01af..5777b5c4025 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -63,7 +63,7 @@ describe ProjectsHelper do end end - describe "#project_list_cache_key", clean_gitlab_redis_shared_state: true do + describe "#project_list_cache_key", :clean_gitlab_redis_shared_state do let(:project) { create(:project, :repository) } it "includes the route" do @@ -200,13 +200,13 @@ describe ProjectsHelper do end it 'returns image tag for member avatar' do - expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16"], alt: "" }) + expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16"], alt: "", "data-src" => anything }) helper.link_to_member_avatar(user) end it 'returns image tag with avatar class' do - expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16", "any-avatar-class"], alt: "" }) + expect(helper).to receive(:image_tag).with(expected, { width: 16, class: ["avatar", "avatar-inline", "s16", "any-avatar-class"], alt: "", "data-src" => anything }) helper.link_to_member_avatar(user, avatar_class: "any-avatar-class") end diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb index 84ad55e9f98..d56e14e0e0b 100644 --- a/spec/initializers/secret_token_spec.rb +++ b/spec/initializers/secret_token_spec.rb @@ -36,10 +36,10 @@ describe 'create_tokens' do expect(keys).to all(match(HEX_KEY)) end - it 'generates an RSA key for jws_private_key' do + it 'generates an RSA key for openid_connect_signing_key' do create_tokens - keys = secrets.values_at(:jws_private_key) + keys = secrets.values_at(:openid_connect_signing_key) expect(keys.uniq).to eq(keys) expect(keys).to all(match(RSA_KEY)) @@ -49,7 +49,7 @@ describe 'create_tokens' do expect(self).to receive(:warn_missing_secret).with('secret_key_base') expect(self).to receive(:warn_missing_secret).with('otp_key_base') expect(self).to receive(:warn_missing_secret).with('db_key_base') - expect(self).to receive(:warn_missing_secret).with('jws_private_key') + expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key') create_tokens end @@ -61,7 +61,7 @@ describe 'create_tokens' do expect(new_secrets['secret_key_base']).to eq(secrets.secret_key_base) expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base) expect(new_secrets['db_key_base']).to eq(secrets.db_key_base) - expect(new_secrets['jws_private_key']).to eq(secrets.jws_private_key) + expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key) end create_tokens @@ -77,7 +77,7 @@ describe 'create_tokens' do context 'when the other secrets all exist' do before do secrets.db_key_base = 'db_key_base' - secrets.jws_private_key = 'jws_private_key' + secrets.openid_connect_signing_key = 'openid_connect_signing_key' allow(File).to receive(:exist?).with('.secret').and_return(true) allow(File).to receive(:read).with('.secret').and_return('file_key') @@ -88,7 +88,7 @@ describe 'create_tokens' do stub_env('SECRET_KEY_BASE', 'env_key') secrets.secret_key_base = 'secret_key_base' secrets.otp_key_base = 'otp_key_base' - secrets.jws_private_key = 'jws_private_key' + secrets.openid_connect_signing_key = 'openid_connect_signing_key' end it 'does not issue a warning' do @@ -114,7 +114,7 @@ describe 'create_tokens' do before do secrets.secret_key_base = 'secret_key_base' secrets.otp_key_base = 'otp_key_base' - secrets.jws_private_key = 'jws_private_key' + secrets.openid_connect_signing_key = 'openid_connect_signing_key' end it 'does not write any files' do @@ -129,7 +129,7 @@ describe 'create_tokens' do expect(secrets.secret_key_base).to eq('secret_key_base') expect(secrets.otp_key_base).to eq('otp_key_base') expect(secrets.db_key_base).to eq('db_key_base') - expect(secrets.jws_private_key).to eq('jws_private_key') + expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key') end it 'deletes the .secret file' do @@ -153,7 +153,7 @@ describe 'create_tokens' do expect(new_secrets['secret_key_base']).to eq('file_key') expect(new_secrets['otp_key_base']).to eq('file_key') expect(new_secrets['db_key_base']).to eq('db_key_base') - expect(new_secrets['jws_private_key']).to eq('jws_private_key') + expect(new_secrets['openid_connect_signing_key']).to eq('openid_connect_signing_key') end create_tokens diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index 9a974e70e8c..a11824d0ac5 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -18,26 +18,6 @@ describe Settings do end end - describe '#repositories' do - it 'assigns the default failure attributes' do - repository_settings = Gitlab.config.repositories.storages['broken'] - - expect(repository_settings['failure_count_threshold']).to eq(10) - expect(repository_settings['failure_wait_time']).to eq(30) - expect(repository_settings['failure_reset_time']).to eq(1800) - expect(repository_settings['storage_timeout']).to eq(5) - end - - it 'can be accessed with dot syntax all the way down' do - expect(Gitlab.config.repositories.storages.broken.failure_count_threshold).to eq(10) - end - - it 'can be accessed in a very specific way that breaks without reassigning each element with Settingslogic' do - storage_settings = Gitlab.config.repositories.storages['broken'] - expect(storage_settings.failure_count_threshold).to eq(10) - end - end - describe '#host_without_www' do context 'URL with protocol' do it 'returns the host' do diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/abuse_reports_spec.js index 13cab81dd60..7f6b5873011 100644 --- a/spec/javascripts/abuse_reports_spec.js +++ b/spec/javascripts/abuse_reports_spec.js @@ -1,43 +1,41 @@ import '~/lib/utils/text_utility'; -import '~/abuse_reports'; - -((global) => { - describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; - const MAX_MESSAGE_LENGTH = 500; - - let $messages; - - const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); - const findMessage = searchText => $messages.filter( - (index, element) => element.innerText.indexOf(searchText) > -1, - ).first(); - - preloadFixtures(FIXTURE); - - beforeEach(function () { - loadFixtures(FIXTURE); - this.abuseReports = new global.AbuseReports(); - $messages = $('.abuse-reports .message'); - }); - - it('should truncate long messages', () => { - const $longMessage = findMessage('LONG MESSAGE'); - expect($longMessage.data('original-message')).toEqual(jasmine.anything()); - assertMaxLength($longMessage); - }); - - it('should not truncate short messages', () => { - const $shortMessage = findMessage('SHORT MESSAGE'); - expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything()); - }); - - it('should allow clicking a truncated message to expand and collapse the full message', () => { - const $longMessage = findMessage('LONG MESSAGE'); - $longMessage.click(); - expect($longMessage.data('original-message').length).toEqual($longMessage.text().length); - $longMessage.click(); - assertMaxLength($longMessage); - }); +import AbuseReports from '~/abuse_reports'; + +describe('Abuse Reports', () => { + const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const MAX_MESSAGE_LENGTH = 500; + + let $messages; + + const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); + const findMessage = searchText => $messages.filter( + (index, element) => element.innerText.indexOf(searchText) > -1, + ).first(); + + preloadFixtures(FIXTURE); + + beforeEach(function () { + loadFixtures(FIXTURE); + this.abuseReports = new AbuseReports(); + $messages = $('.abuse-reports .message'); + }); + + it('should truncate long messages', () => { + const $longMessage = findMessage('LONG MESSAGE'); + expect($longMessage.data('original-message')).toEqual(jasmine.anything()); + assertMaxLength($longMessage); + }); + + it('should not truncate short messages', () => { + const $shortMessage = findMessage('SHORT MESSAGE'); + expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything()); + }); + + it('should allow clicking a truncated message to expand and collapse the full message', () => { + const $longMessage = findMessage('LONG MESSAGE'); + $longMessage.click(); + expect($longMessage.data('original-message').length).toEqual($longMessage.text().length); + $longMessage.click(); + assertMaxLength($longMessage); }); -})(window.gl); +}); diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 46e072a8ebb..c93b7cc6cac 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -1,6 +1,6 @@ import 'jquery'; import 'jquery-ujs'; -import '~/ajax_loading_spinner'; +import AjaxLoadingSpinner from '~/ajax_loading_spinner'; describe('Ajax Loading Spinner', () => { const fixtureTemplate = 'static/ajax_loading_spinner.html.raw'; @@ -8,7 +8,7 @@ describe('Ajax Loading Spinner', () => { beforeEach(() => { loadFixtures(fixtureTemplate); - gl.AjaxLoadingSpinner.init(); + AjaxLoadingSpinner.init(); }); it('change current icon with spinner icon and disable link while waiting ajax response', (done) => { diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index a22b71fd1dc..268b5b83b73 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -28,7 +28,7 @@ import '~/lib/utils/common_utils'; preloadFixtures('merge_requests/diff_comment.html.raw'); beforeEach(function(done) { loadFixtures('merge_requests/diff_comment.html.raw'); - $('body').data('page', 'projects:merge_requests:show'); + $('body').attr('data-page', 'projects:merge_requests:show'); loadAwardsHandler(true).then((obj) => { awardsHandler = obj; spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb()); @@ -55,6 +55,9 @@ import '~/lib/utils/common_utils'; // restore original url root value gon.relative_url_root = urlRoot; + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); + awardsHandler.destroy(); }); describe('::showEmojiMenu', function() { diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index f62bf43adb9..d5300d9c63d 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -19,6 +19,11 @@ describe('Quick Submit behavior', () => { this.textarea = $('.js-quick-submit textarea').first(); }); + afterEach(() => { + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); + }); + it('does not respond to other keyCodes', () => { this.textarea.trigger(keydownEvent({ keyCode: 32, diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js index 2c8183ff77b..47de63e6690 100644 --- a/spec/javascripts/blob/blob_file_dropzone_spec.js +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -1,4 +1,3 @@ -import 'dropzone'; import BlobFileDropzone from '~/blob/blob_file_dropzone'; describe('BlobFileDropzone', () => { diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index 11f2a950678..c3e67550f05 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -117,7 +117,7 @@ describe('iPython notebook renderer', () => { it('shows error message', () => { expect( document.querySelector('.md').textContent.trim(), - ).toBe('An error occured whilst parsing the file.'); + ).toBe('An error occurred whilst parsing the file.'); }); }); @@ -153,7 +153,7 @@ describe('iPython notebook renderer', () => { it('shows error message', () => { expect( document.querySelector('.md').textContent.trim(), - ).toBe('An error occured whilst loading the file. Please try again later.'); + ).toBe('An error occurred whilst loading the file. Please try again later.'); }); }); }); diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index bbeaf95e68d..51bf3086627 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -76,7 +76,7 @@ describe('PDF renderer', () => { it('shows error message', () => { expect( document.querySelector('.md').textContent.trim(), - ).toBe('An error occured whilst loading the file. Please try again later.'); + ).toBe('An error occurred whilst loading the file. Please try again later.'); }); }); }); diff --git a/spec/javascripts/clusters_spec.js b/spec/javascripts/clusters_spec.js new file mode 100644 index 00000000000..eb1cd6eb804 --- /dev/null +++ b/spec/javascripts/clusters_spec.js @@ -0,0 +1,79 @@ +import Clusters from '~/clusters'; + +describe('Clusters', () => { + let cluster; + preloadFixtures('clusters/show_cluster.html.raw'); + + beforeEach(() => { + loadFixtures('clusters/show_cluster.html.raw'); + cluster = new Clusters(); + }); + + describe('toggle', () => { + it('should update the button and the input field on click', () => { + cluster.toggleButton.click(); + + expect( + cluster.toggleButton.classList, + ).not.toContain('checked'); + + expect( + cluster.toggleInput.getAttribute('value'), + ).toEqual('false'); + }); + }); + + describe('updateContainer', () => { + describe('when creating cluster', () => { + it('should show the creating container', () => { + cluster.updateContainer('creating'); + + expect( + cluster.creatingContainer.classList.contains('hidden'), + ).toBeFalsy(); + expect( + cluster.successContainer.classList.contains('hidden'), + ).toBeTruthy(); + expect( + cluster.errorContainer.classList.contains('hidden'), + ).toBeTruthy(); + }); + }); + + describe('when cluster is created', () => { + it('should show the success container', () => { + cluster.updateContainer('created'); + + expect( + cluster.creatingContainer.classList.contains('hidden'), + ).toBeTruthy(); + expect( + cluster.successContainer.classList.contains('hidden'), + ).toBeFalsy(); + expect( + cluster.errorContainer.classList.contains('hidden'), + ).toBeTruthy(); + }); + }); + + describe('when cluster has error', () => { + it('should show the error container', () => { + cluster.updateContainer('errored', 'this is an error'); + + expect( + cluster.creatingContainer.classList.contains('hidden'), + ).toBeTruthy(); + expect( + cluster.successContainer.classList.contains('hidden'), + ).toBeTruthy(); + expect( + cluster.errorContainer.classList.contains('hidden'), + ).toBeFalsy(); + + expect( + cluster.errorReasonContainer.textContent, + ).toContain('this is an error'); + }); + }); + }); +}); diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js index ace95000468..e5a5e3293b9 100644 --- a/spec/javascripts/commits_spec.js +++ b/spec/javascripts/commits_spec.js @@ -1,77 +1,73 @@ -/* global CommitsList */ - import 'vendor/jquery.endless-scroll'; import '~/pager'; -import '~/commits'; - -(() => { - describe('Commits List', () => { - beforeEach(() => { - setFixtures(` - <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master"> - <input id="commits-search"> - </form> - <ol id="commits-list"></ol> - `); - }); +import CommitsList from '~/commits'; - it('should be defined', () => { - expect(CommitsList).toBeDefined(); - }); +describe('Commits List', () => { + beforeEach(() => { + setFixtures(` + <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master"> + <input id="commits-search"> + </form> + <ol id="commits-list"></ol> + `); + }); - describe('processCommits', () => { - it('should join commit headers', () => { - CommitsList.$contentList = $(` - <div> - <li class="commit-header" data-day="2016-09-20"> - <span class="day">20 Sep, 2016</span> - <span class="commits-count">1 commit</span> - </li> - <li class="commit"></li> - </div> - `); + it('should be defined', () => { + expect(CommitsList).toBeDefined(); + }); - const data = ` + describe('processCommits', () => { + it('should join commit headers', () => { + CommitsList.$contentList = $(` + <div> <li class="commit-header" data-day="2016-09-20"> <span class="day">20 Sep, 2016</span> <span class="commits-count">1 commit</span> </li> <li class="commit"></li> - `; + </div> + `); - // The last commit header should be removed - // since the previous one has the same data-day value. - expect(CommitsList.processCommits(data).find('li.commit-header').length).toBe(0); - }); + const data = ` + <li class="commit-header" data-day="2016-09-20"> + <span class="day">20 Sep, 2016</span> + <span class="commits-count">1 commit</span> + </li> + <li class="commit"></li> + `; + + // The last commit header should be removed + // since the previous one has the same data-day value. + expect(CommitsList.processCommits(data).find('li.commit-header').length).toBe(0); }); + }); - describe('on entering input', () => { - let ajaxSpy; + describe('on entering input', () => { + let ajaxSpy; - beforeEach(() => { - CommitsList.init(25); - CommitsList.searchField.val(''); + beforeEach(() => { + CommitsList.init(25); + CommitsList.searchField.val(''); - spyOn(history, 'replaceState').and.stub(); - ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { - req.success({ - data: '<li>Result</li>', - }); + spyOn(history, 'replaceState').and.stub(); + ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { + req.success({ + data: '<li>Result</li>', }); }); + }); - it('should save the last search string', () => { - CommitsList.searchField.val('GitLab'); - CommitsList.filterResults(); - expect(ajaxSpy).toHaveBeenCalled(); - expect(CommitsList.lastSearch).toEqual('GitLab'); - }); + it('should save the last search string', () => { + CommitsList.searchField.val('GitLab'); + CommitsList.filterResults(); + expect(ajaxSpy).toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual('GitLab'); + }); - it('should not make ajax call if the input does not change', () => { - CommitsList.filterResults(); - expect(ajaxSpy).not.toHaveBeenCalled(); - expect(CommitsList.lastSearch).toEqual(''); - }); + it('should not make ajax call if the input does not change', () => { + CommitsList.filterResults(); + expect(ajaxSpy).not.toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual(''); }); }); -})(); +}); diff --git a/spec/javascripts/cycle_analytics/banner_spec.js b/spec/javascripts/cycle_analytics/banner_spec.js new file mode 100644 index 00000000000..fb6b7fee168 --- /dev/null +++ b/spec/javascripts/cycle_analytics/banner_spec.js @@ -0,0 +1,41 @@ +import Vue from 'vue'; +import banner from '~/cycle_analytics/components/banner.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Cycle analytics banner', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(banner); + vm = mountComponent(Component, { + documentationLink: 'path', + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render cycle analytics information', () => { + expect( + vm.$el.querySelector('h4').textContent.trim(), + ).toEqual('Introducing Cycle Analytics'); + expect( + vm.$el.querySelector('p').textContent.trim(), + ).toContain('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.'); + expect( + vm.$el.querySelector('a').textContent.trim(), + ).toEqual('Read more'); + expect( + vm.$el.querySelector('a').getAttribute('href'), + ).toEqual('path'); + }); + + it('should emit an event when close button is clicked', () => { + spyOn(vm, '$emit'); + + vm.$el.querySelector('.js-ca-dismiss-button').click(); + + expect(vm.$emit).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js index 2fb9eb0ca85..13e9fe00a00 100644 --- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js +++ b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import limitWarningComp from '~/cycle_analytics/components/limit_warning_component'; +import limitWarningComp from '~/cycle_analytics/components/limit_warning_component.vue'; Vue.use(Translate); diff --git a/spec/javascripts/cycle_analytics/total_time_component_spec.js b/spec/javascripts/cycle_analytics/total_time_component_spec.js new file mode 100644 index 00000000000..31b65fd1cde --- /dev/null +++ b/spec/javascripts/cycle_analytics/total_time_component_spec.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import component from '~/cycle_analytics/components/total_time_component.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Total time component', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(component); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('With data', () => { + it('should render information for days and hours', () => { + vm = mountComponent(Component, { + time: { + days: 3, + hours: 4, + }, + }); + + expect(vm.$el.textContent.trim()).toEqual('3 days 4 hrs'); + }); + + it('should render information for hours and minutes', () => { + vm = mountComponent(Component, { + time: { + hours: 4, + mins: 35, + }, + }); + + expect(vm.$el.textContent.trim()).toEqual('4 hrs 35 mins'); + }); + + it('should render information for seconds', () => { + vm = mountComponent(Component, { + time: { + seconds: 45, + }, + }); + + expect(vm.$el.textContent.trim()).toEqual('45 s'); + }); + }); + + describe('Without data', () => { + it('should render no information', () => { + vm = mountComponent(Component); + + expect(vm.$el.textContent.trim()).toEqual('--'); + }); + }); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 67166802c70..2ecb64d84b5 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -791,6 +791,29 @@ describe('Filtered Search Visual Tokens', () => { expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name); const avatar = tokenValueElement.querySelector('img.avatar'); expect(avatar.src).toBe(dummyUser.avatar_url); + expect(avatar.alt).toBe(''); + }) + .then(done) + .catch(done.fail); + }); + + it('escapes user name when creating token', (done) => { + const dummyUser = { + name: '<script>', + avatar_url: `${gl.TEST_HOST}/mypics/avatar.png`, + }; + const { tokenValueContainer, tokenValueElement } = findElements(authorToken); + const tokenValue = tokenValueElement.innerText; + usersCacheSpy = (username) => { + expect(`@${username}`).toBe(tokenValue); + return Promise.resolve(dummyUser); + }; + + subject.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) + .then(() => { + expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name); + tokenValueElement.querySelector('.avatar').remove(); + expect(tokenValueElement.innerHTML.trim()).toBe(_.escape(dummyUser.name)); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb new file mode 100644 index 00000000000..5774f36f026 --- /dev/null +++ b/spec/javascripts/fixtures/clusters.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project, :repository, namespace: namespace) } + let(:cluster) { project.create_cluster!(gcp_cluster_name: "gke-test-creation-1", gcp_project_id: 'gitlab-internal-153318', gcp_cluster_zone: 'us-central1-a', gcp_cluster_size: '1', project_namespace: 'aaa', gcp_machine_type: 'n1-standard-1')} + + render_views + + before(:all) do + clean_frontend_fixtures('clusters/') + end + + before do + sign_in(admin) + end + + after do + remove_repository(project) + end + + it 'clusters/show_cluster.html.raw' do |example| + get :show, + namespace_id: project.namespace.to_param, + project_id: project, + id: cluster + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 4bc2205e642..3fd16d76f51 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -41,6 +41,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end + it 'merge_requests/merge_request_of_current_user.html.raw' do |example| + merge_request.update(author: admin) + + render_merge_request(example.description, merge_request) + end + it 'merge_requests/merge_request_with_task_list.html.raw' do |example| create(:ci_build, :pending, pipeline: pipeline) diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js new file mode 100644 index 00000000000..b669aabcee4 --- /dev/null +++ b/spec/javascripts/flash_spec.js @@ -0,0 +1,290 @@ +import flash, { + createFlashEl, + createAction, + hideFlash, + removeFlashClickListener, +} from '~/flash'; + +describe('Flash', () => { + describe('createFlashEl', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + afterEach(() => { + el.innerHTML = ''; + }); + + it('creates flash element with type', () => { + el.innerHTML = createFlashEl('testing', 'alert'); + + expect( + el.querySelector('.flash-alert'), + ).not.toBeNull(); + }); + + it('escapes text', () => { + el.innerHTML = createFlashEl('<script>alert("a");</script>', 'alert'); + + expect( + el.querySelector('.flash-text').textContent.trim(), + ).toBe('<script>alert("a");</script>'); + }); + + it('adds container classes when inside content wrapper', () => { + el.innerHTML = createFlashEl('testing', 'alert', true); + + expect( + el.querySelector('.flash-text').classList.contains('container-fluid'), + ).toBeTruthy(); + expect( + el.querySelector('.flash-text').classList.contains('container-limited'), + ).toBeTruthy(); + }); + }); + + describe('hideFlash', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + el.className = 'js-testing'; + }); + + it('sets transition style', () => { + hideFlash(el); + + expect( + el.style.transition, + ).toBe('opacity 0.3s'); + }); + + it('sets opacity style', () => { + hideFlash(el); + + expect( + el.style.opacity, + ).toBe('0'); + }); + + it('does not set styles when fadeTransition is false', () => { + hideFlash(el, false); + + expect( + el.style.opacity, + ).toBe(''); + expect( + el.style.transition, + ).toBe(''); + }); + + it('removes element after transitionend', () => { + document.body.appendChild(el); + + hideFlash(el); + el.dispatchEvent(new Event('transitionend')); + + expect( + document.querySelector('.js-testing'), + ).toBeNull(); + }); + + it('calls event listener callback once', () => { + spyOn(el, 'remove').and.callThrough(); + document.body.appendChild(el); + + hideFlash(el); + + el.dispatchEvent(new Event('transitionend')); + el.dispatchEvent(new Event('transitionend')); + + expect( + el.remove.calls.count(), + ).toBe(1); + }); + }); + + describe('createAction', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('creates link with href', () => { + el.innerHTML = createAction({ + href: 'testing', + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('testing'); + }); + + it('uses hash as href when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').href, + ).toContain('#'); + }); + + it('adds role when no href is present', () => { + el.innerHTML = createAction({ + title: 'test', + }); + + expect( + el.querySelector('.flash-action').getAttribute('role'), + ).toBe('button'); + }); + + it('escapes the title text', () => { + el.innerHTML = createAction({ + title: '<script>alert("a")</script>', + }); + + expect( + el.querySelector('.flash-action').textContent.trim(), + ).toBe('<script>alert("a")</script>'); + }); + }); + + describe('createFlash', () => { + describe('no flash-container', () => { + it('does not add to the DOM', () => { + const flashEl = flash('testing'); + + expect( + flashEl, + ).toBeNull(); + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + }); + + describe('with flash-container', () => { + beforeEach(() => { + document.body.innerHTML += ` + <div class="content-wrapper js-content-wrapper"> + <div class="flash-container"></div> + </div> + `; + }); + + afterEach(() => { + document.querySelector('.js-content-wrapper').remove(); + }); + + it('adds flash element into container', () => { + flash('test'); + + expect( + document.querySelector('.flash-alert'), + ).not.toBeNull(); + }); + + it('adds flash into specified parent', () => { + flash( + 'test', + 'alert', + document.querySelector('.content-wrapper'), + ); + + expect( + document.querySelector('.content-wrapper .flash-alert'), + ).not.toBeNull(); + }); + + it('adds container classes when inside content-wrapper', () => { + flash('test'); + + expect( + document.querySelector('.flash-text').className, + ).toBe('flash-text container-fluid container-limited'); + }); + + it('does not add container when outside of content-wrapper', () => { + document.querySelector('.content-wrapper').className = 'js-content-wrapper'; + flash('test'); + + expect( + document.querySelector('.flash-text').className.trim(), + ).toBe('flash-text'); + }); + + it('removes element after clicking', () => { + flash('test', 'alert', document, null, false); + + document.querySelector('.flash-alert').click(); + + expect( + document.querySelector('.flash-alert'), + ).toBeNull(); + }); + + describe('with actionConfig', () => { + it('adds action link', () => { + flash( + 'test', + 'alert', + document, + { + title: 'test', + }, + ); + + expect( + document.querySelector('.flash-action'), + ).not.toBeNull(); + }); + + it('calls actionConfig clickHandler on click', () => { + const actionConfig = { + title: 'test', + clickHandler: jasmine.createSpy('actionConfig'), + }; + + flash( + 'test', + 'alert', + document, + actionConfig, + ); + + document.querySelector('.flash-action').click(); + + expect( + actionConfig.clickHandler, + ).toHaveBeenCalled(); + }); + }); + }); + }); + + describe('removeFlashClickListener', () => { + beforeEach(() => { + document.body.innerHTML += '<div class="flash-container"><div class="flash"></div></div>'; + }); + + it('removes global flash on click', (done) => { + const flashEl = document.querySelector('.flash'); + + removeFlashClickListener(flashEl, false); + + flashEl.parentNode.click(); + + setTimeout(() => { + expect(document.querySelector('.flash')).toBeNull(); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index fa24aa426b6..2779686a6f5 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -1,110 +1,108 @@ /* eslint-disable space-before-function-paren, arrow-body-style */ -import '~/gl_field_errors'; +import GlFieldErrors from '~/gl_field_errors'; -((global) => { +describe('GL Style Field Errors', function() { preloadFixtures('static/gl_field_errors.html.raw'); - describe('GL Style Field Errors', function() { - beforeEach(function() { - loadFixtures('static/gl_field_errors.html.raw'); - const $form = this.$form = $('form.gl-show-field-errors'); - this.fieldErrors = new global.GlFieldErrors($form); - }); + beforeEach(function() { + loadFixtures('static/gl_field_errors.html.raw'); + const $form = this.$form = $('form.gl-show-field-errors'); + this.fieldErrors = new GlFieldErrors($form); + }); - it('should select the correct input elements', function() { - expect(this.$form).toBeDefined(); - expect(this.$form.length).toBe(1); - expect(this.fieldErrors).toBeDefined(); - const inputs = this.fieldErrors.state.inputs; - expect(inputs.length).toBe(4); - }); + it('should select the correct input elements', function() { + expect(this.$form).toBeDefined(); + expect(this.$form.length).toBe(1); + expect(this.fieldErrors).toBeDefined(); + const inputs = this.fieldErrors.state.inputs; + expect(inputs.length).toBe(4); + }); - it('should ignore elements with custom error handling', function() { - const customErrorFlag = 'gl-field-error-ignore'; - const customErrorElem = $(`.${customErrorFlag}`); + it('should ignore elements with custom error handling', function() { + const customErrorFlag = 'gl-field-error-ignore'; + const customErrorElem = $(`.${customErrorFlag}`); - expect(customErrorElem.length).toBe(1); + expect(customErrorElem.length).toBe(1); - const customErrors = this.fieldErrors.state.inputs.filter((input) => { - return input.inputElement.hasClass(customErrorFlag); - }); - expect(customErrors.length).toBe(0); + const customErrors = this.fieldErrors.state.inputs.filter((input) => { + return input.inputElement.hasClass(customErrorFlag); }); + expect(customErrors.length).toBe(0); + }); - it('should not show any errors before submit attempt', function() { - this.$form.find('.email').val('not-a-valid-email').keyup(); - this.$form.find('.text-required').val('').keyup(); - this.$form.find('.alphanumberic').val('?---*').keyup(); + it('should not show any errors before submit attempt', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); - const errorsShown = this.$form.find('.gl-field-error-outline'); - expect(errorsShown.length).toBe(0); - }); + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(0); + }); - it('should show errors when input valid is submitted', function() { - this.$form.find('.email').val('not-a-valid-email').keyup(); - this.$form.find('.text-required').val('').keyup(); - this.$form.find('.alphanumberic').val('?---*').keyup(); + it('should show errors when input valid is submitted', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); - this.$form.submit(); + this.$form.submit(); - const errorsShown = this.$form.find('.gl-field-error-outline'); - expect(errorsShown.length).toBe(4); - }); + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(4); + }); - it('should properly track validity state on input after invalid submission attempt', function() { - this.$form.submit(); - - const emailInputModel = this.fieldErrors.state.inputs[1]; - const fieldState = emailInputModel.state; - const emailInputElement = emailInputModel.inputElement; - - // No input - expect(emailInputElement).toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(true); - expect(fieldState.valid).toBe(false); - - // Then invalid input - emailInputElement.val('not-a-valid-email').keyup(); - expect(emailInputElement).toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(false); - expect(fieldState.valid).toBe(false); - - // Then valid input - emailInputElement.val('email@gitlab.com').keyup(); - expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(false); - expect(fieldState.valid).toBe(true); - - // Then invalid input - emailInputElement.val('not-a-valid-email').keyup(); - expect(emailInputElement).toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(false); - expect(fieldState.valid).toBe(false); - - // Then empty input - emailInputElement.val('').keyup(); - expect(emailInputElement).toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(true); - expect(fieldState.valid).toBe(false); - - // Then valid input - emailInputElement.val('email@gitlab.com').keyup(); - expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); - expect(fieldState.empty).toBe(false); - expect(fieldState.valid).toBe(true); - }); + it('should properly track validity state on input after invalid submission attempt', function() { + this.$form.submit(); + + const emailInputModel = this.fieldErrors.state.inputs[1]; + const fieldState = emailInputModel.state; + const emailInputElement = emailInputModel.inputElement; + + // No input + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then empty input + emailInputElement.val('').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + }); - it('should properly infer error messages', function() { - this.$form.submit(); - const trackedInputs = this.fieldErrors.state.inputs; - const inputHasTitle = trackedInputs[1]; - const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error'); - const inputNoTitle = trackedInputs[2]; - const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error'); + it('should properly infer error messages', function() { + this.$form.submit(); + const trackedInputs = this.fieldErrors.state.inputs; + const inputHasTitle = trackedInputs[1]; + const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error'); + const inputNoTitle = trackedInputs[2]; + const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error'); - expect(noTitleErrorElem.text()).toBe('This field is required.'); - expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.'); - }); + expect(noTitleErrorElem.text()).toBe('This field is required.'); + expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.'); }); -})(window.gl || (window.gl = {})); +}); diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js index 837feacec1d..5a8009e57fd 100644 --- a/spec/javascripts/gl_form_spec.js +++ b/spec/javascripts/gl_form_spec.js @@ -1,18 +1,11 @@ -import autosize from 'vendor/autosize'; -import '~/gl_form'; +import Autosize from 'autosize'; +import GLForm from '~/gl_form'; import '~/lib/utils/text_utility'; import '~/lib/utils/common_utils'; -window.autosize = autosize; +window.autosize = Autosize; describe('GLForm', () => { - const global = window.gl || (window.gl = {}); - const GLForm = global.GLForm; - - it('should be defined in the global scope', () => { - expect(GLForm).toBeDefined(); - }); - describe('when instantiated', function () { beforeEach((done) => { this.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>'); diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js new file mode 100644 index 00000000000..59d4f7c45c6 --- /dev/null +++ b/spec/javascripts/groups/components/app_spec.js @@ -0,0 +1,443 @@ +import Vue from 'vue'; + +import appComponent from '~/groups/components/app.vue'; +import groupFolderComponent from '~/groups/components/group_folder.vue'; +import groupItemComponent from '~/groups/components/group_item.vue'; + +import eventHub from '~/groups/event_hub'; +import GroupsStore from '~/groups/store/groups_store'; +import GroupsService from '~/groups/service/groups_service'; + +import { + mockEndpoint, mockGroups, mockSearchedGroups, + mockRawPageInfo, mockParentGroupItem, mockRawChildren, + mockChildren, mockPageInfo, +} from '../mock_data'; + +const createComponent = (hideProjects = false) => { + const Component = Vue.extend(appComponent); + const store = new GroupsStore(false); + const service = new GroupsService(mockEndpoint); + + return new Component({ + propsData: { + store, + service, + hideProjects, + }, + }); +}; + +const returnServicePromise = (data, failed) => new Promise((resolve, reject) => { + if (failed) { + reject(data); + } else { + resolve({ + json() { + return data; + }, + }); + } +}); + +describe('AppComponent', () => { + let vm; + + beforeEach((done) => { + Vue.component('group-folder', groupFolderComponent); + Vue.component('group-item', groupItemComponent); + + vm = createComponent(); + + Vue.nextTick(() => { + done(); + }); + }); + + describe('computed', () => { + beforeEach(() => { + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('groups', () => { + it('should return list of groups from store', () => { + spyOn(vm.store, 'getGroups'); + + const groups = vm.groups; + expect(vm.store.getGroups).toHaveBeenCalled(); + expect(groups).not.toBeDefined(); + }); + }); + + describe('pageInfo', () => { + it('should return pagination info from store', () => { + spyOn(vm.store, 'getPaginationInfo'); + + const pageInfo = vm.pageInfo; + expect(vm.store.getPaginationInfo).toHaveBeenCalled(); + expect(pageInfo).not.toBeDefined(); + }); + }); + }); + + describe('methods', () => { + beforeEach(() => { + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('fetchGroups', () => { + it('should call `getGroups` with all the params provided', (done) => { + spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(mockGroups)); + + vm.fetchGroups({ + parentId: 1, + page: 2, + filterGroupsBy: 'git', + sortBy: 'created_desc', + archived: true, + }); + setTimeout(() => { + expect(vm.service.getGroups).toHaveBeenCalledWith(1, 2, 'git', 'created_desc', true); + done(); + }, 0); + }); + + it('should set headers to store for building pagination info when called with `updatePagination`', (done) => { + spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise({ headers: mockRawPageInfo })); + spyOn(vm, 'updatePagination'); + + vm.fetchGroups({ updatePagination: true }); + setTimeout(() => { + expect(vm.service.getGroups).toHaveBeenCalled(); + expect(vm.updatePagination).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('should show flash error when request fails', (done) => { + spyOn(vm.service, 'getGroups').and.returnValue(returnServicePromise(null, true)); + spyOn($, 'scrollTo'); + spyOn(window, 'Flash'); + + vm.fetchGroups({}); + setTimeout(() => { + expect(vm.isLoading).toBeFalsy(); + expect($.scrollTo).toHaveBeenCalledWith(0); + expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.'); + done(); + }, 0); + }); + }); + + describe('fetchAllGroups', () => { + it('should fetch default set of groups', (done) => { + spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups)); + spyOn(vm, 'updatePagination').and.callThrough(); + spyOn(vm, 'updateGroups').and.callThrough(); + + vm.fetchAllGroups(); + expect(vm.isLoading).toBeTruthy(); + expect(vm.fetchGroups).toHaveBeenCalled(); + setTimeout(() => { + expect(vm.isLoading).toBeFalsy(); + expect(vm.updateGroups).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('should fetch matching set of groups when app is loaded with search query', (done) => { + spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockSearchedGroups)); + spyOn(vm, 'updateGroups').and.callThrough(); + + vm.fetchAllGroups(); + expect(vm.fetchGroups).toHaveBeenCalledWith({ + page: null, + filterGroupsBy: null, + sortBy: null, + updatePagination: true, + archived: null, + }); + setTimeout(() => { + expect(vm.updateGroups).toHaveBeenCalled(); + done(); + }, 0); + }); + }); + + describe('fetchPage', () => { + it('should fetch groups for provided page details and update window state', (done) => { + spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups)); + spyOn(vm, 'updateGroups').and.callThrough(); + spyOn(gl.utils, 'mergeUrlParams').and.callThrough(); + spyOn(window.history, 'replaceState'); + spyOn($, 'scrollTo'); + + vm.fetchPage(2, null, null, true); + expect(vm.isLoading).toBeTruthy(); + expect(vm.fetchGroups).toHaveBeenCalledWith({ + page: 2, + filterGroupsBy: null, + sortBy: null, + updatePagination: true, + archived: true, + }); + setTimeout(() => { + expect(vm.isLoading).toBeFalsy(); + expect($.scrollTo).toHaveBeenCalledWith(0); + expect(gl.utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String)); + expect(window.history.replaceState).toHaveBeenCalledWith({ + page: jasmine.any(String), + }, jasmine.any(String), jasmine.any(String)); + expect(vm.updateGroups).toHaveBeenCalled(); + done(); + }, 0); + }); + }); + + describe('toggleChildren', () => { + let groupItem; + + beforeEach(() => { + groupItem = Object.assign({}, mockParentGroupItem); + groupItem.isOpen = false; + groupItem.isChildrenLoading = false; + }); + + it('should fetch children of given group and expand it if group is collapsed and children are not loaded', (done) => { + spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockRawChildren)); + spyOn(vm.store, 'setGroupChildren'); + + vm.toggleChildren(groupItem); + expect(groupItem.isChildrenLoading).toBeTruthy(); + expect(vm.fetchGroups).toHaveBeenCalledWith({ + parentId: groupItem.id, + }); + setTimeout(() => { + expect(vm.store.setGroupChildren).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('should skip network request while expanding group if children are already loaded', () => { + spyOn(vm, 'fetchGroups'); + groupItem.children = mockRawChildren; + + vm.toggleChildren(groupItem); + expect(vm.fetchGroups).not.toHaveBeenCalled(); + expect(groupItem.isOpen).toBeTruthy(); + }); + + it('should collapse group if it is already expanded', () => { + spyOn(vm, 'fetchGroups'); + groupItem.isOpen = true; + + vm.toggleChildren(groupItem); + expect(vm.fetchGroups).not.toHaveBeenCalled(); + expect(groupItem.isOpen).toBeFalsy(); + }); + + it('should set `isChildrenLoading` back to `false` if load request fails', (done) => { + spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true)); + + vm.toggleChildren(groupItem); + expect(groupItem.isChildrenLoading).toBeTruthy(); + setTimeout(() => { + expect(groupItem.isChildrenLoading).toBeFalsy(); + done(); + }, 0); + }); + }); + + describe('leaveGroup', () => { + let groupItem; + let childGroupItem; + + beforeEach(() => { + groupItem = Object.assign({}, mockParentGroupItem); + groupItem.children = mockChildren; + childGroupItem = groupItem.children[0]; + groupItem.isChildrenLoading = false; + }); + + it('should leave group and remove group item from tree', (done) => { + const notice = `You left the "${childGroupItem.fullName}" group.`; + spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice })); + spyOn(vm.store, 'removeGroup').and.callThrough(); + spyOn(window, 'Flash'); + spyOn($, 'scrollTo'); + + vm.leaveGroup(childGroupItem, groupItem); + expect(childGroupItem.isBeingRemoved).toBeTruthy(); + expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); + setTimeout(() => { + expect($.scrollTo).toHaveBeenCalledWith(0); + expect(vm.store.removeGroup).toHaveBeenCalledWith(childGroupItem, groupItem); + expect(window.Flash).toHaveBeenCalledWith(notice, 'notice'); + done(); + }, 0); + }); + + it('should show error flash message if request failed to leave group', (done) => { + const message = 'An error occurred. Please try again.'; + spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 500 }, true)); + spyOn(vm.store, 'removeGroup').and.callThrough(); + spyOn(window, 'Flash'); + + vm.leaveGroup(childGroupItem, groupItem); + expect(childGroupItem.isBeingRemoved).toBeTruthy(); + expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); + setTimeout(() => { + expect(vm.store.removeGroup).not.toHaveBeenCalled(); + expect(window.Flash).toHaveBeenCalledWith(message); + expect(childGroupItem.isBeingRemoved).toBeFalsy(); + done(); + }, 0); + }); + + it('should show appropriate error flash message if request forbids to leave group', (done) => { + const message = 'Failed to leave the group. Please make sure you are not the only owner.'; + spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ status: 403 }, true)); + spyOn(vm.store, 'removeGroup').and.callThrough(); + spyOn(window, 'Flash'); + + vm.leaveGroup(childGroupItem, groupItem); + expect(childGroupItem.isBeingRemoved).toBeTruthy(); + expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); + setTimeout(() => { + expect(vm.store.removeGroup).not.toHaveBeenCalled(); + expect(window.Flash).toHaveBeenCalledWith(message); + expect(childGroupItem.isBeingRemoved).toBeFalsy(); + done(); + }, 0); + }); + }); + + describe('updatePagination', () => { + it('should set pagination info to store from provided headers', () => { + spyOn(vm.store, 'setPaginationInfo'); + + vm.updatePagination(mockRawPageInfo); + expect(vm.store.setPaginationInfo).toHaveBeenCalledWith(mockRawPageInfo); + }); + }); + + describe('updateGroups', () => { + it('should call setGroups on store if method was called directly', () => { + spyOn(vm.store, 'setGroups'); + + vm.updateGroups(mockGroups); + expect(vm.store.setGroups).toHaveBeenCalledWith(mockGroups); + }); + + it('should call setSearchedGroups on store if method was called with fromSearch param', () => { + spyOn(vm.store, 'setSearchedGroups'); + + vm.updateGroups(mockGroups, true); + expect(vm.store.setSearchedGroups).toHaveBeenCalledWith(mockGroups); + }); + + it('should set `isSearchEmpty` prop based on groups count', () => { + vm.updateGroups(mockGroups); + expect(vm.isSearchEmpty).toBeFalsy(); + + vm.updateGroups([]); + expect(vm.isSearchEmpty).toBeTruthy(); + }); + }); + }); + + describe('created', () => { + it('should bind event listeners on eventHub', (done) => { + spyOn(eventHub, '$on'); + + const newVm = createComponent(); + newVm.$mount(); + + Vue.nextTick(() => { + expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', jasmine.any(Function)); + expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function)); + expect(eventHub.$on).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function)); + expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', jasmine.any(Function)); + expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', jasmine.any(Function)); + newVm.$destroy(); + done(); + }); + }); + + it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', (done) => { + const newVm = createComponent(); + newVm.$mount(); + Vue.nextTick(() => { + expect(newVm.searchEmptyMessage).toBe('Sorry, no groups or projects matched your search'); + newVm.$destroy(); + done(); + }); + }); + + it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', (done) => { + const newVm = createComponent(true); + newVm.$mount(); + Vue.nextTick(() => { + expect(newVm.searchEmptyMessage).toBe('Sorry, no groups matched your search'); + newVm.$destroy(); + done(); + }); + }); + }); + + describe('beforeDestroy', () => { + it('should unbind event listeners on eventHub', (done) => { + spyOn(eventHub, '$off'); + + const newVm = createComponent(); + newVm.$mount(); + newVm.$destroy(); + + Vue.nextTick(() => { + expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', jasmine.any(Function)); + expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function)); + expect(eventHub.$off).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function)); + expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', jasmine.any(Function)); + expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', jasmine.any(Function)); + done(); + }); + }); + }); + + describe('template', () => { + beforeEach(() => { + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render loading icon', (done) => { + vm.isLoading = true; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.loading-animation')).toBeDefined(); + expect(vm.$el.querySelector('i.fa').getAttribute('aria-label')).toBe('Loading groups'); + done(); + }); + }); + + it('should render groups tree', (done) => { + vm.store.state.groups = [mockParentGroupItem]; + vm.isLoading = false; + vm.store.state.pageInfo = mockPageInfo; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined(); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/groups/components/group_folder_spec.js b/spec/javascripts/groups/components/group_folder_spec.js new file mode 100644 index 00000000000..4eb198595fb --- /dev/null +++ b/spec/javascripts/groups/components/group_folder_spec.js @@ -0,0 +1,66 @@ +import Vue from 'vue'; + +import groupFolderComponent from '~/groups/components/group_folder.vue'; +import groupItemComponent from '~/groups/components/group_item.vue'; +import { mockGroups, mockParentGroupItem } from '../mock_data'; + +const createComponent = (groups = mockGroups, parentGroup = mockParentGroupItem) => { + const Component = Vue.extend(groupFolderComponent); + + return new Component({ + propsData: { + groups, + parentGroup, + }, + }); +}; + +describe('GroupFolderComponent', () => { + let vm; + + beforeEach((done) => { + Vue.component('group-item', groupItemComponent); + + vm = createComponent(); + vm.$mount(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('hasMoreChildren', () => { + it('should return false when childrenCount of group is less than MAX_CHILDREN_COUNT', () => { + expect(vm.hasMoreChildren).toBeFalsy(); + }); + }); + + describe('moreChildrenStats', () => { + it('should return message with count of excess children over MAX_CHILDREN_COUNT limit', () => { + expect(vm.moreChildrenStats).toBe('3 more items'); + }); + }); + }); + + describe('template', () => { + it('should render component template correctly', () => { + expect(vm.$el.classList.contains('group-list-tree')).toBeTruthy(); + expect(vm.$el.querySelectorAll('li.group-row').length).toBe(7); + }); + + it('should render more children link when groups list has children over MAX_CHILDREN_COUNT limit', () => { + const parentGroup = Object.assign({}, mockParentGroupItem); + parentGroup.childrenCount = 21; + + const newVm = createComponent(mockGroups, parentGroup); + newVm.$mount(); + expect(newVm.$el.querySelector('li.group-row a.has-more-items')).toBeDefined(); + newVm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js new file mode 100644 index 00000000000..0f4fbdae445 --- /dev/null +++ b/spec/javascripts/groups/components/group_item_spec.js @@ -0,0 +1,177 @@ +import Vue from 'vue'; + +import groupItemComponent from '~/groups/components/group_item.vue'; +import groupFolderComponent from '~/groups/components/group_folder.vue'; +import eventHub from '~/groups/event_hub'; +import { mockParentGroupItem, mockChildren } from '../mock_data'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (group = mockParentGroupItem, parentGroup = mockChildren[0]) => { + const Component = Vue.extend(groupItemComponent); + + return mountComponent(Component, { + group, + parentGroup, + }); +}; + +describe('GroupItemComponent', () => { + let vm; + + beforeEach((done) => { + Vue.component('group-folder', groupFolderComponent); + + vm = createComponent(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('groupDomId', () => { + it('should return ID string suffixed with group ID', () => { + expect(vm.groupDomId).toBe('group-55'); + }); + }); + + describe('rowClass', () => { + it('should return map of classes based on group details', () => { + const classes = ['is-open', 'has-children', 'has-description', 'being-removed']; + const rowClass = vm.rowClass; + + expect(Object.keys(rowClass).length).toBe(classes.length); + Object.keys(rowClass).forEach((className) => { + expect(classes.indexOf(className) > -1).toBeTruthy(); + }); + }); + }); + + describe('hasChildren', () => { + it('should return boolean value representing if group has any children present', () => { + let newVm; + const group = Object.assign({}, mockParentGroupItem); + + group.childrenCount = 5; + newVm = createComponent(group); + expect(newVm.hasChildren).toBeTruthy(); + newVm.$destroy(); + + group.childrenCount = 0; + newVm = createComponent(group); + expect(newVm.hasChildren).toBeFalsy(); + newVm.$destroy(); + }); + }); + + describe('hasAvatar', () => { + it('should return boolean value representing if group has any avatar present', () => { + let newVm; + const group = Object.assign({}, mockParentGroupItem); + + group.avatarUrl = null; + newVm = createComponent(group); + expect(newVm.hasAvatar).toBeFalsy(); + newVm.$destroy(); + + group.avatarUrl = '/uploads/group_avatar.png'; + newVm = createComponent(group); + expect(newVm.hasAvatar).toBeTruthy(); + newVm.$destroy(); + }); + }); + + describe('isGroup', () => { + it('should return boolean value representing if group item is of type `group` or not', () => { + let newVm; + const group = Object.assign({}, mockParentGroupItem); + + group.type = 'group'; + newVm = createComponent(group); + expect(newVm.isGroup).toBeTruthy(); + newVm.$destroy(); + + group.type = 'project'; + newVm = createComponent(group); + expect(newVm.isGroup).toBeFalsy(); + newVm.$destroy(); + }); + }); + }); + + describe('methods', () => { + describe('onClickRowGroup', () => { + let event; + + beforeEach(() => { + const classList = { + contains() { + return false; + }, + }; + + event = { + target: { + classList, + parentElement: { + classList, + }, + }, + }; + }); + + it('should emit `toggleChildren` event when expand is clicked on a group and it has children present', () => { + spyOn(eventHub, '$emit'); + + vm.onClickRowGroup(event); + expect(eventHub.$emit).toHaveBeenCalledWith('toggleChildren', vm.group); + }); + + it('should navigate page to group homepage if group does not have any children present', (done) => { + const group = Object.assign({}, mockParentGroupItem); + group.childrenCount = 0; + const newVm = createComponent(group); + spyOn(gl.utils, 'visitUrl').and.stub(); + spyOn(eventHub, '$emit'); + + newVm.onClickRowGroup(event); + setTimeout(() => { + expect(eventHub.$emit).not.toHaveBeenCalled(); + expect(gl.utils.visitUrl).toHaveBeenCalledWith(newVm.group.relativePath); + done(); + }, 0); + }); + }); + }); + + describe('template', () => { + it('should render component template correctly', () => { + expect(vm.$el.getAttribute('id')).toBe('group-55'); + expect(vm.$el.classList.contains('group-row')).toBeTruthy(); + + expect(vm.$el.querySelector('.group-row-contents')).toBeDefined(); + expect(vm.$el.querySelector('.group-row-contents .controls')).toBeDefined(); + expect(vm.$el.querySelector('.group-row-contents .stats')).toBeDefined(); + + expect(vm.$el.querySelector('.folder-toggle-wrap')).toBeDefined(); + expect(vm.$el.querySelector('.folder-toggle-wrap .folder-caret')).toBeDefined(); + expect(vm.$el.querySelector('.folder-toggle-wrap .item-type-icon')).toBeDefined(); + + expect(vm.$el.querySelector('.avatar-container')).toBeDefined(); + expect(vm.$el.querySelector('.avatar-container a.no-expand')).toBeDefined(); + expect(vm.$el.querySelector('.avatar-container .avatar')).toBeDefined(); + + expect(vm.$el.querySelector('.title')).toBeDefined(); + expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined(); + expect(vm.$el.querySelector('.access-type')).toBeDefined(); + expect(vm.$el.querySelector('.description')).toBeDefined(); + + expect(vm.$el.querySelector('.group-list-tree')).toBeDefined(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/groups_spec.js b/spec/javascripts/groups/components/groups_spec.js new file mode 100644 index 00000000000..90e818c1545 --- /dev/null +++ b/spec/javascripts/groups/components/groups_spec.js @@ -0,0 +1,70 @@ +import Vue from 'vue'; + +import groupsComponent from '~/groups/components/groups.vue'; +import groupFolderComponent from '~/groups/components/group_folder.vue'; +import groupItemComponent from '~/groups/components/group_item.vue'; +import eventHub from '~/groups/event_hub'; +import { mockGroups, mockPageInfo } from '../mock_data'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (searchEmpty = false) => { + const Component = Vue.extend(groupsComponent); + + return mountComponent(Component, { + groups: mockGroups, + pageInfo: mockPageInfo, + searchEmptyMessage: 'No matching results', + searchEmpty, + }); +}; + +describe('GroupsComponent', () => { + let vm; + + beforeEach((done) => { + Vue.component('group-folder', groupFolderComponent); + Vue.component('group-item', groupItemComponent); + + vm = createComponent(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('methods', () => { + describe('change', () => { + it('should emit `fetchPage` event when page is changed via pagination', () => { + spyOn(eventHub, '$emit').and.stub(); + + vm.change(2); + expect(eventHub.$emit).toHaveBeenCalledWith('fetchPage', 2, jasmine.any(Object), jasmine.any(Object), jasmine.any(Object)); + }); + }); + }); + + describe('template', () => { + it('should render component template correctly', (done) => { + Vue.nextTick(() => { + expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined(); + expect(vm.$el.querySelector('.group-list-tree')).toBeDefined(); + expect(vm.$el.querySelector('.gl-pagination')).toBeDefined(); + expect(vm.$el.querySelectorAll('.has-no-search-results').length === 0).toBeTruthy(); + done(); + }); + }); + + it('should render empty search message when `searchEmpty` is `true`', (done) => { + vm.searchEmpty = true; + Vue.nextTick(() => { + expect(vm.$el.querySelector('.has-no-search-results')).toBeDefined(); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js new file mode 100644 index 00000000000..2ce1a749a96 --- /dev/null +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -0,0 +1,110 @@ +import Vue from 'vue'; + +import itemActionsComponent from '~/groups/components/item_actions.vue'; +import eventHub from '~/groups/event_hub'; +import { mockParentGroupItem, mockChildren } from '../mock_data'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (group = mockParentGroupItem, parentGroup = mockChildren[0]) => { + const Component = Vue.extend(itemActionsComponent); + + return mountComponent(Component, { + group, + parentGroup, + }); +}; + +describe('ItemActionsComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('leaveConfirmationMessage', () => { + it('should return appropriate string for leave group confirmation', () => { + expect(vm.leaveConfirmationMessage).toBe('Are you sure you want to leave the "platform / hardware" group?'); + }); + }); + }); + + describe('methods', () => { + describe('onLeaveGroup', () => { + it('should change `dialogStatus` prop to `true` which shows confirmation dialog', () => { + expect(vm.dialogStatus).toBeFalsy(); + vm.onLeaveGroup(); + expect(vm.dialogStatus).toBeTruthy(); + }); + }); + + describe('leaveGroup', () => { + it('should change `dialogStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { + spyOn(eventHub, '$emit'); + vm.dialogStatus = true; + vm.leaveGroup(true); + expect(vm.dialogStatus).toBeFalsy(); + expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); + }); + + it('should change `dialogStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { + spyOn(eventHub, '$emit'); + vm.dialogStatus = true; + vm.leaveGroup(false); + expect(vm.dialogStatus).toBeFalsy(); + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + }); + }); + + describe('template', () => { + it('should render component template correctly', () => { + expect(vm.$el.classList.contains('controls')).toBeTruthy(); + }); + + it('should render Edit Group button with correct attribute values', () => { + const group = Object.assign({}, mockParentGroupItem); + group.canEdit = true; + const newVm = createComponent(group); + + const editBtn = newVm.$el.querySelector('a.edit-group'); + expect(editBtn).toBeDefined(); + expect(editBtn.classList.contains('no-expand')).toBeTruthy(); + expect(editBtn.getAttribute('href')).toBe(group.editPath); + expect(editBtn.getAttribute('aria-label')).toBe('Edit group'); + expect(editBtn.dataset.originalTitle).toBe('Edit group'); + expect(editBtn.querySelector('i.fa.fa-cogs')).toBeDefined(); + + newVm.$destroy(); + }); + + it('should render Leave Group button with correct attribute values', () => { + const group = Object.assign({}, mockParentGroupItem); + group.canLeave = true; + const newVm = createComponent(group); + + const leaveBtn = newVm.$el.querySelector('a.leave-group'); + expect(leaveBtn).toBeDefined(); + expect(leaveBtn.classList.contains('no-expand')).toBeTruthy(); + expect(leaveBtn.getAttribute('href')).toBe(group.leavePath); + expect(leaveBtn.getAttribute('aria-label')).toBe('Leave this group'); + expect(leaveBtn.dataset.originalTitle).toBe('Leave this group'); + expect(leaveBtn.querySelector('i.fa.fa-sign-out')).toBeDefined(); + + newVm.$destroy(); + }); + + it('should show modal dialog when `dialogStatus` is set to `true`', () => { + vm.dialogStatus = true; + const modalDialogEl = vm.$el.querySelector('.modal.popup-dialog'); + expect(modalDialogEl).toBeDefined(); + expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); + expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_caret_spec.js b/spec/javascripts/groups/components/item_caret_spec.js new file mode 100644 index 00000000000..4310a07e6e6 --- /dev/null +++ b/spec/javascripts/groups/components/item_caret_spec.js @@ -0,0 +1,40 @@ +import Vue from 'vue'; + +import itemCaretComponent from '~/groups/components/item_caret.vue'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (isGroupOpen = false) => { + const Component = Vue.extend(itemCaretComponent); + + return mountComponent(Component, { + isGroupOpen, + }); +}; + +describe('ItemCaretComponent', () => { + describe('template', () => { + it('should render component template correctly', () => { + const vm = createComponent(); + vm.$mount(); + expect(vm.$el.classList.contains('folder-caret')).toBeTruthy(); + vm.$destroy(); + }); + + it('should render caret down icon if `isGroupOpen` prop is `true`', () => { + const vm = createComponent(true); + vm.$mount(); + expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(1); + expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(0); + vm.$destroy(); + }); + + it('should render caret right icon if `isGroupOpen` prop is `false`', () => { + const vm = createComponent(); + vm.$mount(); + expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(0); + expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(1); + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js new file mode 100644 index 00000000000..e200f9f08bd --- /dev/null +++ b/spec/javascripts/groups/components/item_stats_spec.js @@ -0,0 +1,159 @@ +import Vue from 'vue'; + +import itemStatsComponent from '~/groups/components/item_stats.vue'; +import { + mockParentGroupItem, + ITEM_TYPE, + VISIBILITY_TYPE_ICON, + GROUP_VISIBILITY_TYPE, + PROJECT_VISIBILITY_TYPE, +} from '../mock_data'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (item = mockParentGroupItem) => { + const Component = Vue.extend(itemStatsComponent); + + return mountComponent(Component, { + item, + }); +}; + +describe('ItemStatsComponent', () => { + describe('computed', () => { + describe('visibilityIcon', () => { + it('should return icon class based on `item.visibility` value', () => { + Object.keys(VISIBILITY_TYPE_ICON).forEach((visibility) => { + const item = Object.assign({}, mockParentGroupItem, { visibility }); + const vm = createComponent(item); + vm.$mount(); + expect(vm.visibilityIcon).toBe(VISIBILITY_TYPE_ICON[visibility]); + vm.$destroy(); + }); + }); + }); + + describe('visibilityTooltip', () => { + it('should return tooltip string for Group based on `item.visibility` value', () => { + Object.keys(GROUP_VISIBILITY_TYPE).forEach((visibility) => { + const item = Object.assign({}, mockParentGroupItem, { + visibility, + type: ITEM_TYPE.GROUP, + }); + const vm = createComponent(item); + vm.$mount(); + expect(vm.visibilityTooltip).toBe(GROUP_VISIBILITY_TYPE[visibility]); + vm.$destroy(); + }); + }); + + it('should return tooltip string for Project based on `item.visibility` value', () => { + Object.keys(PROJECT_VISIBILITY_TYPE).forEach((visibility) => { + const item = Object.assign({}, mockParentGroupItem, { + visibility, + type: ITEM_TYPE.PROJECT, + }); + const vm = createComponent(item); + vm.$mount(); + expect(vm.visibilityTooltip).toBe(PROJECT_VISIBILITY_TYPE[visibility]); + vm.$destroy(); + }); + }); + }); + + describe('isProject', () => { + it('should return boolean value representing whether `item.type` is Project or not', () => { + let item; + let vm; + + item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); + vm = createComponent(item); + vm.$mount(); + expect(vm.isProject).toBeTruthy(); + vm.$destroy(); + + item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); + vm = createComponent(item); + vm.$mount(); + expect(vm.isProject).toBeFalsy(); + vm.$destroy(); + }); + }); + + describe('isGroup', () => { + it('should return boolean value representing whether `item.type` is Group or not', () => { + let item; + let vm; + + item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); + vm = createComponent(item); + vm.$mount(); + expect(vm.isGroup).toBeTruthy(); + vm.$destroy(); + + item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT }); + vm = createComponent(item); + vm.$mount(); + expect(vm.isGroup).toBeFalsy(); + vm.$destroy(); + }); + }); + }); + + describe('template', () => { + it('should render component template correctly', () => { + const vm = createComponent(); + vm.$mount(); + + const visibilityIconEl = vm.$el.querySelector('.item-visibility'); + expect(vm.$el.classList.contains('.stats')).toBeDefined(); + expect(visibilityIconEl).toBeDefined(); + expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); + expect(visibilityIconEl.querySelector('i.fa')).toBeDefined(); + + vm.$destroy(); + }); + + it('should render stat icons if `item.type` is Group', () => { + const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.GROUP }); + const vm = createComponent(item); + vm.$mount(); + + const subgroupIconEl = vm.$el.querySelector('span.number-subgroups'); + expect(subgroupIconEl).toBeDefined(); + expect(subgroupIconEl.dataset.originalTitle).toBe('Subgroups'); + expect(subgroupIconEl.querySelector('i.fa.fa-folder')).toBeDefined(); + expect(subgroupIconEl.innerText.trim()).toBe(`${vm.item.subgroupCount}`); + + const projectsIconEl = vm.$el.querySelector('span.number-projects'); + expect(projectsIconEl).toBeDefined(); + expect(projectsIconEl.dataset.originalTitle).toBe('Projects'); + expect(projectsIconEl.querySelector('i.fa.fa-bookmark')).toBeDefined(); + expect(projectsIconEl.innerText.trim()).toBe(`${vm.item.projectCount}`); + + const membersIconEl = vm.$el.querySelector('span.number-users'); + expect(membersIconEl).toBeDefined(); + expect(membersIconEl.dataset.originalTitle).toBe('Members'); + expect(membersIconEl.querySelector('i.fa.fa-users')).toBeDefined(); + expect(membersIconEl.innerText.trim()).toBe(`${vm.item.memberCount}`); + + vm.$destroy(); + }); + + it('should render stat icons if `item.type` is Project', () => { + const item = Object.assign({}, mockParentGroupItem, { + type: ITEM_TYPE.PROJECT, + starCount: 4, + }); + const vm = createComponent(item); + vm.$mount(); + + const projectStarIconEl = vm.$el.querySelector('.project-stars'); + expect(projectStarIconEl).toBeDefined(); + expect(projectStarIconEl.querySelector('i.fa.fa-star')).toBeDefined(); + expect(projectStarIconEl.innerText.trim()).toBe(`${vm.item.starCount}`); + + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/components/item_type_icon_spec.js b/spec/javascripts/groups/components/item_type_icon_spec.js new file mode 100644 index 00000000000..528e6ed1b4c --- /dev/null +++ b/spec/javascripts/groups/components/item_type_icon_spec.js @@ -0,0 +1,54 @@ +import Vue from 'vue'; + +import itemTypeIconComponent from '~/groups/components/item_type_icon.vue'; +import { ITEM_TYPE } from '../mock_data'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = (itemType = ITEM_TYPE.GROUP, isGroupOpen = false) => { + const Component = Vue.extend(itemTypeIconComponent); + + return mountComponent(Component, { + itemType, + isGroupOpen, + }); +}; + +describe('ItemTypeIconComponent', () => { + describe('template', () => { + it('should render component template correctly', () => { + const vm = createComponent(); + vm.$mount(); + expect(vm.$el.classList.contains('item-type-icon')).toBeTruthy(); + vm.$destroy(); + }); + + it('should render folder open or close icon based `isGroupOpen` prop value', () => { + let vm; + + vm = createComponent(ITEM_TYPE.GROUP, true); + vm.$mount(); + expect(vm.$el.querySelector('i.fa.fa-folder-open')).toBeDefined(); + vm.$destroy(); + + vm = createComponent(ITEM_TYPE.GROUP); + vm.$mount(); + expect(vm.$el.querySelector('i.fa.fa-folder')).toBeDefined(); + vm.$destroy(); + }); + + it('should render bookmark icon based on `isProject` prop value', () => { + let vm; + + vm = createComponent(ITEM_TYPE.PROJECT); + vm.$mount(); + expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(1); + vm.$destroy(); + + vm = createComponent(ITEM_TYPE.GROUP); + vm.$mount(); + expect(vm.$el.querySelectorAll('i.fa.fa-bookmark').length).toBe(0); + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/groups/group_item_spec.js b/spec/javascripts/groups/group_item_spec.js deleted file mode 100644 index 25e10552d95..00000000000 --- a/spec/javascripts/groups/group_item_spec.js +++ /dev/null @@ -1,102 +0,0 @@ -import Vue from 'vue'; -import groupItemComponent from '~/groups/components/group_item.vue'; -import GroupsStore from '~/groups/stores/groups_store'; -import { group1 } from './mock_data'; - -describe('Groups Component', () => { - let GroupItemComponent; - let component; - let store; - let group; - - describe('group with default data', () => { - beforeEach((done) => { - GroupItemComponent = Vue.extend(groupItemComponent); - store = new GroupsStore(); - group = store.decorateGroup(group1); - - component = new GroupItemComponent({ - propsData: { - group, - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - }); - - afterEach(() => { - component.$destroy(); - }); - - it('should render the group item correctly', () => { - expect(component.$el.classList.contains('group-row')).toBe(true); - expect(component.$el.classList.contains('.no-description')).toBe(false); - expect(component.$el.querySelector('.number-projects').textContent).toContain(group.numberProjects); - expect(component.$el.querySelector('.number-users').textContent).toContain(group.numberUsers); - expect(component.$el.querySelector('.group-visibility')).toBeDefined(); - expect(component.$el.querySelector('.avatar-container')).toBeDefined(); - expect(component.$el.querySelector('.title').textContent).toContain(group.name); - expect(component.$el.querySelector('.access-type').textContent).toContain(group.permissions.humanGroupAccess); - expect(component.$el.querySelector('.description').textContent).toContain(group.description); - expect(component.$el.querySelector('.edit-group')).toBeDefined(); - expect(component.$el.querySelector('.leave-group')).toBeDefined(); - }); - }); - - describe('group without description', () => { - beforeEach((done) => { - GroupItemComponent = Vue.extend(groupItemComponent); - store = new GroupsStore(); - group1.description = ''; - group = store.decorateGroup(group1); - - component = new GroupItemComponent({ - propsData: { - group, - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - }); - - afterEach(() => { - component.$destroy(); - }); - - it('should render group item correctly', () => { - expect(component.$el.querySelector('.description').textContent).toBe(''); - expect(component.$el.classList.contains('.no-description')).toBe(false); - }); - }); - - describe('user has not access to group', () => { - beforeEach((done) => { - GroupItemComponent = Vue.extend(groupItemComponent); - store = new GroupsStore(); - group1.permissions.human_group_access = null; - group = store.decorateGroup(group1); - - component = new GroupItemComponent({ - propsData: { - group, - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - }); - - afterEach(() => { - component.$destroy(); - }); - - it('should not display access type', () => { - expect(component.$el.querySelector('.access-type')).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/groups/groups_spec.js b/spec/javascripts/groups/groups_spec.js deleted file mode 100644 index b14153dbbfa..00000000000 --- a/spec/javascripts/groups/groups_spec.js +++ /dev/null @@ -1,99 +0,0 @@ -import Vue from 'vue'; -import eventHub from '~/groups/event_hub'; -import groupFolderComponent from '~/groups/components/group_folder.vue'; -import groupItemComponent from '~/groups/components/group_item.vue'; -import groupsComponent from '~/groups/components/groups.vue'; -import GroupsStore from '~/groups/stores/groups_store'; -import { groupsData } from './mock_data'; - -describe('Groups Component', () => { - let GroupsComponent; - let store; - let component; - let groups; - - beforeEach((done) => { - Vue.component('group-folder', groupFolderComponent); - Vue.component('group-item', groupItemComponent); - - store = new GroupsStore(); - groups = store.setGroups(groupsData.groups); - - store.storePagination(groupsData.pagination); - - GroupsComponent = Vue.extend(groupsComponent); - - component = new GroupsComponent({ - propsData: { - groups: store.state.groups, - pageInfo: store.state.pageInfo, - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - }); - - afterEach(() => { - component.$destroy(); - }); - - describe('with data', () => { - it('should render a list of groups', () => { - expect(component.$el.classList.contains('groups-list-tree-container')).toBe(true); - expect(component.$el.querySelector('#group-12')).toBeDefined(); - expect(component.$el.querySelector('#group-1119')).toBeDefined(); - expect(component.$el.querySelector('#group-1120')).toBeDefined(); - }); - - it('should respect the order of groups', () => { - const wrap = component.$el.querySelector('.groups-list-tree-container > .group-list-tree'); - expect(wrap.querySelector('.group-row:nth-child(1)').id).toBe('group-12'); - expect(wrap.querySelector('.group-row:nth-child(2)').id).toBe('group-1119'); - }); - - it('should render group and its subgroup', () => { - const lists = component.$el.querySelectorAll('.group-list-tree'); - - expect(lists.length).toBe(3); // one parent and two subgroups - - expect(lists[0].querySelector('#group-1119').classList.contains('is-open')).toBe(true); - expect(lists[0].querySelector('#group-1119').classList.contains('has-subgroups')).toBe(true); - - expect(lists[2].querySelector('#group-1120').textContent).toContain(groups.id1119.subGroups.id1120.name); - }); - - it('should render group identicon when group avatar is not present', () => { - const avatar = component.$el.querySelector('#group-12 .avatar-container .avatar'); - expect(avatar.nodeName).toBe('DIV'); - expect(avatar.classList.contains('identicon')).toBeTruthy(); - expect(avatar.getAttribute('style').indexOf('background-color') > -1).toBeTruthy(); - }); - - it('should render group avatar when group avatar is present', () => { - const avatar = component.$el.querySelector('#group-1120 .avatar-container .avatar'); - expect(avatar.nodeName).toBe('IMG'); - expect(avatar.classList.contains('identicon')).toBeFalsy(); - }); - - it('should remove prefix of parent group', () => { - expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4'); - }); - - it('should remove the group after leaving the group', (done) => { - spyOn(window, 'confirm').and.returnValue(true); - - eventHub.$on('leaveGroup', (group, collection) => { - store.removeGroup(group, collection); - }); - - component.$el.querySelector('#group-12 .leave-group').click(); - - Vue.nextTick(() => { - expect(component.$el.querySelector('#group-12')).toBeNull(); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js index 5bb84b591f4..6184d671790 100644 --- a/spec/javascripts/groups/mock_data.js +++ b/spec/javascripts/groups/mock_data.js @@ -1,114 +1,380 @@ -const group1 = { - id: 12, - name: 'level1', - path: 'level1', - description: 'foo', - visibility: 'public', - avatar_url: null, - web_url: 'http://localhost:3000/groups/level1', - group_path: '/level1', - full_name: 'level1', - full_path: 'level1', - parent_id: null, - created_at: '2017-05-15T19:01:23.670Z', - updated_at: '2017-05-15T19:01:23.670Z', - number_projects_with_delimiter: '1', - number_users_with_delimiter: '1', - has_subgroups: true, - permissions: { - human_group_access: 'Master', - }, +export const mockEndpoint = '/dashboard/groups.json'; + +export const ITEM_TYPE = { + PROJECT: 'project', + GROUP: 'group', }; -// This group has no direct parent, should be placed as subgroup of group1 -const group14 = { - id: 1128, - name: 'level4', - path: 'level4', - description: 'foo', - visibility: 'public', - avatar_url: null, - web_url: 'http://localhost:3000/groups/level1/level2/level3/level4', - group_path: '/level1/level2/level3/level4', - full_name: 'level1 / level2 / level3 / level4', - full_path: 'level1/level2/level3/level4', - parent_id: 1127, - created_at: '2017-05-15T19:02:01.645Z', - updated_at: '2017-05-15T19:02:01.645Z', - number_projects_with_delimiter: '1', - number_users_with_delimiter: '1', - has_subgroups: true, - permissions: { - human_group_access: 'Master', - }, +export const GROUP_VISIBILITY_TYPE = { + public: 'Public - The group and any public projects can be viewed without any authentication.', + internal: 'Internal - The group and any internal projects can be viewed by any logged in user.', + private: 'Private - The group and its projects can only be viewed by members.', }; -const group2 = { - id: 1119, - name: 'devops', - path: 'devops', - description: 'foo', - visibility: 'public', - avatar_url: null, - web_url: 'http://localhost:3000/groups/devops', - group_path: '/devops', - full_name: 'devops', - full_path: 'devops', - parent_id: null, - created_at: '2017-05-11T19:35:09.635Z', - updated_at: '2017-05-11T19:35:09.635Z', - number_projects_with_delimiter: '1', - number_users_with_delimiter: '1', - has_subgroups: true, - permissions: { - human_group_access: 'Master', - }, +export const PROJECT_VISIBILITY_TYPE = { + public: 'Public - The project can be accessed without any authentication.', + internal: 'Internal - The project can be accessed by any logged in user.', + private: 'Private - Project access must be granted explicitly to each user.', +}; + +export const VISIBILITY_TYPE_ICON = { + public: 'fa-globe', + internal: 'fa-shield', + private: 'fa-lock', }; -const group21 = { - id: 1120, - name: 'chef', - path: 'chef', - description: 'foo', +export const mockParentGroupItem = { + id: 55, + name: 'hardware', + description: '', visibility: 'public', - avatar_url: '/uploads/-/system/group/avatar/2/GitLab.png', - web_url: 'http://localhost:3000/groups/devops/chef', - group_path: '/devops/chef', - full_name: 'devops / chef', - full_path: 'devops/chef', - parent_id: 1119, - created_at: '2017-05-11T19:51:04.060Z', - updated_at: '2017-05-11T19:51:04.060Z', - number_projects_with_delimiter: '1', - number_users_with_delimiter: '1', - has_subgroups: true, - permissions: { - human_group_access: 'Master', - }, + fullName: 'platform / hardware', + relativePath: '/platform/hardware', + canEdit: true, + type: 'group', + avatarUrl: null, + permission: 'Owner', + editPath: '/groups/platform/hardware/edit', + childrenCount: 3, + leavePath: '/groups/platform/hardware/group_members/leave', + parentId: 54, + memberCount: '1', + projectCount: 1, + subgroupCount: 2, + canLeave: false, + children: [], + isOpen: true, + isChildrenLoading: false, + isBeingRemoved: false, }; -const groupsData = { - groups: [group1, group14, group2, group21], - pagination: { - Date: 'Mon, 22 May 2017 22:31:52 GMT', - 'X-Prev-Page': '1', - 'X-Content-Type-Options': 'nosniff', - 'X-Total': '31', - 'Transfer-Encoding': 'chunked', - 'X-Runtime': '0.611144', - 'X-Xss-Protection': '1; mode=block', - 'X-Request-Id': 'f5db8368-3ce5-4aa4-89d2-a125d9dead09', - 'X-Ua-Compatible': 'IE=edge', - 'X-Per-Page': '20', - Link: '<http://localhost:3000/dashboard/groups.json?page=1&per_page=20>; rel="prev", <http://localhost:3000/dashboard/groups.json?page=1&per_page=20>; rel="first", <http://localhost:3000/dashboard/groups.json?page=2&per_page=20>; rel="last"', - 'X-Next-Page': '', - Etag: 'W/"a82f846947136271cdb7d55d19ef33d2"', - 'X-Frame-Options': 'DENY', - 'Content-Type': 'application/json; charset=utf-8', - 'Cache-Control': 'max-age=0, private, must-revalidate', - 'X-Total-Pages': '2', - 'X-Page': '2', +export const mockRawChildren = [ + { + id: 57, + name: 'bsp', + description: '', + visibility: 'public', + full_name: 'platform / hardware / bsp', + relative_path: '/platform/hardware/bsp', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/hardware/bsp/edit', + children_count: 6, + leave_path: '/groups/platform/hardware/bsp/group_members/leave', + parent_id: 55, + number_users_with_delimiter: '1', + project_count: 4, + subgroup_count: 2, + can_leave: false, + children: [], + }, +]; + +export const mockChildren = [ + { + id: 57, + name: 'bsp', + description: '', + visibility: 'public', + fullName: 'platform / hardware / bsp', + relativePath: '/platform/hardware/bsp', + canEdit: true, + type: 'group', + avatarUrl: null, + permission: 'Owner', + editPath: '/groups/platform/hardware/bsp/edit', + childrenCount: 6, + leavePath: '/groups/platform/hardware/bsp/group_members/leave', + parentId: 55, + memberCount: '1', + projectCount: 4, + subgroupCount: 2, + canLeave: false, + children: [], + isOpen: true, + isChildrenLoading: false, + isBeingRemoved: false, }, +]; + +export const mockGroups = [ + { + id: 75, + name: 'test-group', + description: '', + visibility: 'public', + full_name: 'test-group', + relative_path: '/test-group', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/test-group/edit', + children_count: 2, + leave_path: '/groups/test-group/group_members/leave', + parent_id: null, + number_users_with_delimiter: '1', + project_count: 2, + subgroup_count: 0, + can_leave: false, + }, + { + id: 67, + name: 'open-source', + description: '', + visibility: 'private', + full_name: 'open-source', + relative_path: '/open-source', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/open-source/edit', + children_count: 0, + leave_path: '/groups/open-source/group_members/leave', + parent_id: null, + number_users_with_delimiter: '1', + project_count: 0, + subgroup_count: 0, + can_leave: false, + }, + { + id: 54, + name: 'platform', + description: '', + visibility: 'public', + full_name: 'platform', + relative_path: '/platform', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/edit', + children_count: 1, + leave_path: '/groups/platform/group_members/leave', + parent_id: null, + number_users_with_delimiter: '1', + project_count: 0, + subgroup_count: 1, + can_leave: false, + }, + { + id: 5, + name: 'H5bp', + description: 'Minus dolor consequuntur qui nam recusandae quam incidunt.', + visibility: 'public', + full_name: 'H5bp', + relative_path: '/h5bp', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/h5bp/edit', + children_count: 1, + leave_path: '/groups/h5bp/group_members/leave', + parent_id: null, + number_users_with_delimiter: '5', + project_count: 1, + subgroup_count: 0, + can_leave: false, + }, + { + id: 4, + name: 'Twitter', + description: 'Deserunt hic nostrum placeat veniam.', + visibility: 'public', + full_name: 'Twitter', + relative_path: '/twitter', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/twitter/edit', + children_count: 2, + leave_path: '/groups/twitter/group_members/leave', + parent_id: null, + number_users_with_delimiter: '5', + project_count: 2, + subgroup_count: 0, + can_leave: false, + }, + { + id: 3, + name: 'Documentcloud', + description: 'Consequatur saepe totam ea pariatur maxime.', + visibility: 'public', + full_name: 'Documentcloud', + relative_path: '/documentcloud', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/documentcloud/edit', + children_count: 1, + leave_path: '/groups/documentcloud/group_members/leave', + parent_id: null, + number_users_with_delimiter: '5', + project_count: 1, + subgroup_count: 0, + can_leave: false, + }, + { + id: 2, + name: 'Gitlab Org', + description: 'Debitis ea quas aperiam velit doloremque ab.', + visibility: 'public', + full_name: 'Gitlab Org', + relative_path: '/gitlab-org', + can_edit: true, + type: 'group', + avatar_url: '/uploads/-/system/group/avatar/2/GitLab.png', + permission: 'Owner', + edit_path: '/groups/gitlab-org/edit', + children_count: 4, + leave_path: '/groups/gitlab-org/group_members/leave', + parent_id: null, + number_users_with_delimiter: '5', + project_count: 4, + subgroup_count: 0, + can_leave: false, + }, +]; + +export const mockSearchedGroups = [ + { + id: 55, + name: 'hardware', + description: '', + visibility: 'public', + full_name: 'platform / hardware', + relative_path: '/platform/hardware', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/hardware/edit', + children_count: 3, + leave_path: '/groups/platform/hardware/group_members/leave', + parent_id: 54, + number_users_with_delimiter: '1', + project_count: 1, + subgroup_count: 2, + can_leave: false, + children: [ + { + id: 57, + name: 'bsp', + description: '', + visibility: 'public', + full_name: 'platform / hardware / bsp', + relative_path: '/platform/hardware/bsp', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/hardware/bsp/edit', + children_count: 6, + leave_path: '/groups/platform/hardware/bsp/group_members/leave', + parent_id: 55, + number_users_with_delimiter: '1', + project_count: 4, + subgroup_count: 2, + can_leave: false, + children: [ + { + id: 60, + name: 'kernel', + description: '', + visibility: 'public', + full_name: 'platform / hardware / bsp / kernel', + relative_path: '/platform/hardware/bsp/kernel', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/hardware/bsp/kernel/edit', + children_count: 1, + leave_path: '/groups/platform/hardware/bsp/kernel/group_members/leave', + parent_id: 57, + number_users_with_delimiter: '1', + project_count: 0, + subgroup_count: 1, + can_leave: false, + children: [ + { + id: 61, + name: 'common', + description: '', + visibility: 'public', + full_name: 'platform / hardware / bsp / kernel / common', + relative_path: '/platform/hardware/bsp/kernel/common', + can_edit: true, + type: 'group', + avatar_url: null, + permission: 'Owner', + edit_path: '/groups/platform/hardware/bsp/kernel/common/edit', + children_count: 2, + leave_path: '/groups/platform/hardware/bsp/kernel/common/group_members/leave', + parent_id: 60, + number_users_with_delimiter: '1', + project_count: 2, + subgroup_count: 0, + can_leave: false, + children: [ + { + id: 17, + name: 'v4.4', + description: 'Voluptatem qui ea error aperiam veritatis doloremque consequatur temporibus.', + visibility: 'public', + full_name: 'platform / hardware / bsp / kernel / common / v4.4', + relative_path: '/platform/hardware/bsp/kernel/common/v4.4', + can_edit: true, + type: 'project', + avatar_url: null, + permission: null, + edit_path: '/platform/hardware/bsp/kernel/common/v4.4/edit', + star_count: 0, + }, + { + id: 16, + name: 'v4.1', + description: 'Rerum expedita voluptatem doloribus neque ducimus ut hic.', + visibility: 'public', + full_name: 'platform / hardware / bsp / kernel / common / v4.1', + relative_path: '/platform/hardware/bsp/kernel/common/v4.1', + can_edit: true, + type: 'project', + avatar_url: null, + permission: null, + edit_path: '/platform/hardware/bsp/kernel/common/v4.1/edit', + star_count: 0, + }, + ], + }, + ], + }, + ], + }, + ], + }, +]; + +export const mockRawPageInfo = { + 'x-per-page': 10, + 'x-page': 10, + 'x-total': 10, + 'x-total-pages': 10, + 'x-next-page': 10, + 'x-prev-page': 10, }; -export { groupsData, group1 }; +export const mockPageInfo = { + perPage: 10, + page: 10, + total: 10, + totalPages: 10, + nextPage: 10, + prevPage: 10, +}; diff --git a/spec/javascripts/groups/service/groups_service_spec.js b/spec/javascripts/groups/service/groups_service_spec.js new file mode 100644 index 00000000000..20bb63687f7 --- /dev/null +++ b/spec/javascripts/groups/service/groups_service_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; + +import GroupsService from '~/groups/service/groups_service'; +import { mockEndpoint, mockParentGroupItem } from '../mock_data'; + +Vue.use(VueResource); + +describe('GroupsService', () => { + let service; + + beforeEach(() => { + service = new GroupsService(mockEndpoint); + }); + + describe('getGroups', () => { + it('should return promise for `GET` request on provided endpoint', () => { + spyOn(service.groups, 'get').and.stub(); + const queryParams = { + page: 2, + filter: 'git', + sort: 'created_asc', + archived: true, + }; + + service.getGroups(55, 2, 'git', 'created_asc', true); + expect(service.groups.get).toHaveBeenCalledWith({ parent_id: 55 }); + + service.getGroups(null, 2, 'git', 'created_asc', true); + expect(service.groups.get).toHaveBeenCalledWith(queryParams); + }); + }); + + describe('leaveGroup', () => { + it('should return promise for `DELETE` request on provided endpoint', () => { + spyOn(Vue.http, 'delete').and.stub(); + + service.leaveGroup(mockParentGroupItem.leavePath); + expect(Vue.http.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath); + }); + }); +}); diff --git a/spec/javascripts/groups/store/groups_store_spec.js b/spec/javascripts/groups/store/groups_store_spec.js new file mode 100644 index 00000000000..d74f38f476e --- /dev/null +++ b/spec/javascripts/groups/store/groups_store_spec.js @@ -0,0 +1,110 @@ +import GroupsStore from '~/groups/store/groups_store'; +import { + mockGroups, mockSearchedGroups, + mockParentGroupItem, mockRawChildren, + mockRawPageInfo, +} from '../mock_data'; + +describe('ProjectsStore', () => { + describe('constructor', () => { + it('should initialize default state', () => { + let store; + + store = new GroupsStore(); + expect(Object.keys(store.state).length).toBe(2); + expect(Array.isArray(store.state.groups)).toBeTruthy(); + expect(Object.keys(store.state.pageInfo).length).toBe(0); + expect(store.hideProjects).not.toBeDefined(); + + store = new GroupsStore(true); + expect(store.hideProjects).toBeTruthy(); + }); + }); + + describe('setGroups', () => { + it('should set groups to state', () => { + const store = new GroupsStore(); + spyOn(store, 'formatGroupItem').and.callThrough(); + + store.setGroups(mockGroups); + expect(store.state.groups.length).toBe(mockGroups.length); + expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); + expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy(); + }); + }); + + describe('setSearchedGroups', () => { + it('should set searched groups to state', () => { + const store = new GroupsStore(); + spyOn(store, 'formatGroupItem').and.callThrough(); + + store.setSearchedGroups(mockSearchedGroups); + expect(store.state.groups.length).toBe(mockSearchedGroups.length); + expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); + expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy(); + expect(Object.keys(store.state.groups[0].children[0]).indexOf('fullName') > -1).toBeTruthy(); + }); + }); + + describe('setGroupChildren', () => { + it('should set children to group item in state', () => { + const store = new GroupsStore(); + spyOn(store, 'formatGroupItem').and.callThrough(); + + store.setGroupChildren(mockParentGroupItem, mockRawChildren); + expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object)); + expect(mockParentGroupItem.children.length).toBe(1); + expect(Object.keys(mockParentGroupItem.children[0]).indexOf('fullName') > -1).toBeTruthy(); + expect(mockParentGroupItem.isOpen).toBeTruthy(); + expect(mockParentGroupItem.isChildrenLoading).toBeFalsy(); + }); + }); + + describe('setPaginationInfo', () => { + it('should parse and set pagination info in state', () => { + const store = new GroupsStore(); + + store.setPaginationInfo(mockRawPageInfo); + expect(store.state.pageInfo.perPage).toBe(10); + expect(store.state.pageInfo.page).toBe(10); + expect(store.state.pageInfo.total).toBe(10); + expect(store.state.pageInfo.totalPages).toBe(10); + expect(store.state.pageInfo.nextPage).toBe(10); + expect(store.state.pageInfo.previousPage).toBe(10); + }); + }); + + describe('formatGroupItem', () => { + it('should parse group item object and return updated object', () => { + let store; + let updatedGroupItem; + + store = new GroupsStore(); + updatedGroupItem = store.formatGroupItem(mockRawChildren[0]); + expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy(); + expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].children_count); + expect(updatedGroupItem.isChildrenLoading).toBe(false); + expect(updatedGroupItem.isBeingRemoved).toBe(false); + + store = new GroupsStore(true); + updatedGroupItem = store.formatGroupItem(mockRawChildren[0]); + expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy(); + expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].subgroup_count); + }); + }); + + describe('removeGroup', () => { + it('should remove children from group item in state', () => { + const store = new GroupsStore(); + const rawParentGroup = Object.assign({}, mockGroups[0]); + const rawChildGroup = Object.assign({}, mockGroups[1]); + + store.setGroups([rawParentGroup]); + store.setGroupChildren(store.state.groups[0], [rawChildGroup]); + const childItem = store.state.groups[0].children[0]; + + store.removeGroup(childItem, store.state.groups[0]); + expect(store.state.groups[0].children.length).toBe(0); + }); + }); +}); diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 0e01934d3a3..2443ffd48f3 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -1,53 +1,49 @@ -/* eslint-disable space-before-function-paren, no-var */ +import initTodoToggle from '~/header'; -import '~/header'; -import '~/lib/utils/text_utility'; +describe('Header', function () { + const todosPendingCount = '.todos-count'; + const fixtureTemplate = 'issues/open-issue.html.raw'; -(function() { - describe('Header', function() { - var todosPendingCount = '.todos-count'; - var fixtureTemplate = 'issues/open-issue.html.raw'; + function isTodosCountHidden() { + return $(todosPendingCount).hasClass('hidden'); + } - function isTodosCountHidden() { - return $(todosPendingCount).hasClass('hidden'); - } + function triggerToggle(newCount) { + $(document).trigger('todo:toggle', newCount); + } - function triggerToggle(newCount) { - $(document).trigger('todo:toggle', newCount); - } + preloadFixtures(fixtureTemplate); + beforeEach(() => { + initTodoToggle(); + loadFixtures(fixtureTemplate); + }); - preloadFixtures(fixtureTemplate); - beforeEach(function() { - loadFixtures(fixtureTemplate); - }); + it('should update todos-count after receiving the todo:toggle event', () => { + triggerToggle('5'); + expect($(todosPendingCount).text()).toEqual('5'); + }); - it('should update todos-count after receiving the todo:toggle event', function() { - triggerToggle(5); - expect($(todosPendingCount).text()).toEqual('5'); - }); + it('should hide todos-count when it is 0', () => { + triggerToggle('0'); + expect(isTodosCountHidden()).toEqual(true); + }); - it('should hide todos-count when it is 0', function() { - triggerToggle(0); - expect(isTodosCountHidden()).toEqual(true); + it('should show todos-count when it is more than 0', () => { + triggerToggle('10'); + expect(isTodosCountHidden()).toEqual(false); + }); + + describe('when todos-count is 1000', () => { + beforeEach(() => { + triggerToggle('1000'); }); - it('should show todos-count when it is more than 0', function() { - triggerToggle(10); + it('should show todos-count', () => { expect(isTodosCountHidden()).toEqual(false); }); - describe('when todos-count is 1000', function() { - beforeEach(function() { - triggerToggle(1000); - }); - - it('should show todos-count', function() { - expect(isTodosCountHidden()).toEqual(false); - }); - - it('should show 99+ for todos-count', function() { - expect($(todosPendingCount).text()).toEqual('99+'); - }); + it('should show 99+ for todos-count', () => { + expect($(todosPendingCount).text()).toEqual('99+'); }); }); -}).call(window); +}); diff --git a/spec/javascripts/helpers/set_timeout_promise_helper.js b/spec/javascripts/helpers/set_timeout_promise_helper.js new file mode 100644 index 00000000000..1478073413c --- /dev/null +++ b/spec/javascripts/helpers/set_timeout_promise_helper.js @@ -0,0 +1,3 @@ +export default (time = 0) => new Promise((resolve) => { + setTimeout(resolve, time); +}); diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js index d7a2e86771c..34acdfbfba9 100644 --- a/spec/javascripts/helpers/vue_mount_component_helper.js +++ b/spec/javascripts/helpers/vue_mount_component_helper.js @@ -1,4 +1,8 @@ -export default (Component, props = {}) => new Component({ - propsData: props, -}).$mount(); +export const createComponentWithStore = (Component, store, propsData = {}) => new Component({ + store, + propsData, +}); +export default (Component, props = {}, el = null) => new Component({ + propsData: props, +}).$mount(el); diff --git a/spec/javascripts/notes/stores/helpers.js b/spec/javascripts/helpers/vuex_action_helper.js index 2d386fe1da5..2d386fe1da5 100644 --- a/spec/javascripts/notes/stores/helpers.js +++ b/spec/javascripts/helpers/vuex_action_helper.js diff --git a/spec/javascripts/image_diff/helpers/badge_helper_spec.js b/spec/javascripts/image_diff/helpers/badge_helper_spec.js new file mode 100644 index 00000000000..fb9c7e59031 --- /dev/null +++ b/spec/javascripts/image_diff/helpers/badge_helper_spec.js @@ -0,0 +1,132 @@ +import * as badgeHelper from '~/image_diff/helpers/badge_helper'; +import * as mockData from '../mock_data'; + +describe('badge helper', () => { + const { coordinate, noteId, badgeText, badgeNumber } = mockData; + let containerEl; + let buttonEl; + + beforeEach(() => { + containerEl = document.createElement('div'); + }); + + describe('createImageBadge', () => { + beforeEach(() => { + buttonEl = badgeHelper.createImageBadge(noteId, coordinate); + }); + + it('should create button', () => { + expect(buttonEl.tagName).toEqual('BUTTON'); + expect(buttonEl.getAttribute('type')).toEqual('button'); + }); + + it('should set disabled attribute', () => { + expect(buttonEl.hasAttribute('disabled')).toEqual(true); + }); + + it('should set noteId', () => { + expect(buttonEl.dataset.noteId).toEqual(noteId); + }); + + it('should set coordinate', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + describe('classNames', () => { + it('should set .js-image-badge by default', () => { + expect(buttonEl.className).toEqual('js-image-badge'); + }); + + it('should add additional class names if parameter is passed', () => { + const classNames = ['first-class', 'second-class']; + buttonEl = badgeHelper.createImageBadge(noteId, coordinate, classNames); + + expect(buttonEl.className).toEqual(classNames.concat('js-image-badge').join(' ')); + }); + }); + }); + + describe('addImageBadge', () => { + beforeEach(() => { + badgeHelper.addImageBadge(containerEl, { + coordinate, + badgeText, + noteId, + }); + buttonEl = containerEl.querySelector('button'); + }); + + it('should appends button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + it('should set the badge text', () => { + expect(buttonEl.innerText).toEqual(badgeText); + }); + + it('should set the button coordinates', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + it('should set the button noteId', () => { + expect(buttonEl.dataset.noteId).toEqual(noteId); + }); + }); + + describe('addImageCommentBadge', () => { + beforeEach(() => { + badgeHelper.addImageCommentBadge(containerEl, { + coordinate, + noteId, + }); + buttonEl = containerEl.querySelector('button'); + }); + + it('should append icon button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + it('should create icon comment button', () => { + const iconEl = buttonEl.querySelector('i'); + expect(iconEl).toBeDefined(); + expect(iconEl.classList.contains('fa')).toEqual(true); + expect(iconEl.classList.contains('fa-comment-o')).toEqual(true); + }); + + it('should have .image-comment-badge.inverted in button class', () => { + expect(buttonEl.classList.contains('image-comment-badge')).toEqual(true); + expect(buttonEl.classList.contains('inverted')).toEqual(true); + }); + }); + + describe('addAvatarBadge', () => { + let avatarBadgeEl; + + beforeEach(() => { + containerEl.innerHTML = ` + <div id="${noteId}"> + <div class="badge hidden"> + </div> + </div> + `; + + badgeHelper.addAvatarBadge(containerEl, { + detail: { + noteId, + badgeNumber, + }, + }); + avatarBadgeEl = containerEl.querySelector(`#${noteId} .badge`); + }); + + it('should update badge number', () => { + expect(avatarBadgeEl.innerText).toEqual(badgeNumber.toString()); + }); + + it('should remove hidden class', () => { + expect(avatarBadgeEl.classList.contains('hidden')).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js new file mode 100644 index 00000000000..a284b981d2a --- /dev/null +++ b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js @@ -0,0 +1,139 @@ +import * as commentIndicatorHelper from '~/image_diff/helpers/comment_indicator_helper'; +import * as mockData from '../mock_data'; + +describe('commentIndicatorHelper', () => { + const { coordinate } = mockData; + let containerEl; + + beforeEach(() => { + containerEl = document.createElement('div'); + }); + + describe('addCommentIndicator', () => { + let buttonEl; + + beforeEach(() => { + commentIndicatorHelper.addCommentIndicator(containerEl, coordinate); + buttonEl = containerEl.querySelector('button'); + }); + + it('should append button to container', () => { + expect(buttonEl).toBeDefined(); + }); + + describe('button', () => { + it('should set coordinate', () => { + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + + it('should contain image-comment-dark svg', () => { + const svgEl = buttonEl.querySelector('svg'); + expect(svgEl).toBeDefined(); + + const svgLink = svgEl.querySelector('use').getAttribute('xlink:href'); + expect(svgLink.indexOf('image-comment-dark') !== -1).toEqual(true); + }); + }); + }); + + describe('removeCommentIndicator', () => { + it('should return removed false if there is no comment-indicator', () => { + const result = commentIndicatorHelper.removeCommentIndicator(containerEl); + expect(result.removed).toEqual(false); + }); + + describe('has comment indicator', () => { + let result; + + beforeEach(() => { + containerEl.innerHTML = ` + <div class="comment-indicator" style="left:${coordinate.x}px; top: ${coordinate.y}px;"> + <img src="${gl.TEST_HOST}/image.png"> + </div> + `; + result = commentIndicatorHelper.removeCommentIndicator(containerEl); + }); + + it('should remove comment indicator', () => { + expect(containerEl.querySelector('.comment-indicator')).toBeNull(); + }); + + it('should return removed true', () => { + expect(result.removed).toEqual(true); + }); + + it('should return indicator meta', () => { + expect(result.x).toEqual(coordinate.x); + expect(result.y).toEqual(coordinate.y); + expect(result.image).toBeDefined(); + expect(result.image.width).toBeDefined(); + expect(result.image.height).toBeDefined(); + }); + }); + }); + + describe('showCommentIndicator', () => { + describe('commentIndicator exists', () => { + beforeEach(() => { + containerEl.innerHTML = ` + <button class="comment-indicator"></button> + `; + commentIndicatorHelper.showCommentIndicator(containerEl, coordinate); + }); + + it('should set commentIndicator coordinates', () => { + const commentIndicatorEl = containerEl.querySelector('.comment-indicator'); + expect(commentIndicatorEl.style.left).toEqual(`${coordinate.x}px`); + expect(commentIndicatorEl.style.top).toEqual(`${coordinate.y}px`); + }); + }); + + describe('commentIndicator does not exist', () => { + beforeEach(() => { + commentIndicatorHelper.showCommentIndicator(containerEl, coordinate); + }); + + it('should addCommentIndicator', () => { + const buttonEl = containerEl.querySelector('.comment-indicator'); + expect(buttonEl).toBeDefined(); + expect(buttonEl.style.left).toEqual(`${coordinate.x}px`); + expect(buttonEl.style.top).toEqual(`${coordinate.y}px`); + }); + }); + }); + + describe('commentIndicatorOnClick', () => { + let event; + let textAreaEl; + + beforeEach(() => { + containerEl.innerHTML = ` + <div class="diff-viewer"> + <button></button> + <div class="note-container"> + <textarea class="note-textarea"></textarea> + </div> + </div> + `; + textAreaEl = containerEl.querySelector('textarea'); + + event = { + stopPropagation: () => {}, + currentTarget: containerEl.querySelector('button'), + }; + + spyOn(event, 'stopPropagation'); + spyOn(textAreaEl, 'focus'); + commentIndicatorHelper.commentIndicatorOnClick(event); + }); + + it('should stopPropagation', () => { + expect(event.stopPropagation).toHaveBeenCalled(); + }); + + it('should focus textAreaEl', () => { + expect(textAreaEl.focus).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/dom_helper_spec.js b/spec/javascripts/image_diff/helpers/dom_helper_spec.js new file mode 100644 index 00000000000..8dde924e8ae --- /dev/null +++ b/spec/javascripts/image_diff/helpers/dom_helper_spec.js @@ -0,0 +1,118 @@ +import * as domHelper from '~/image_diff/helpers/dom_helper'; +import * as mockData from '../mock_data'; + +describe('domHelper', () => { + const { imageMeta, badgeNumber } = mockData; + + describe('setPositionDataAttribute', () => { + let containerEl; + let attributeAfterCall; + const position = { + myProperty: 'myProperty', + }; + + beforeEach(() => { + containerEl = document.createElement('div'); + containerEl.dataset.position = JSON.stringify(position); + domHelper.setPositionDataAttribute(containerEl, imageMeta); + attributeAfterCall = JSON.parse(containerEl.dataset.position); + }); + + it('should set x, y, width, height', () => { + expect(attributeAfterCall.x).toEqual(imageMeta.x); + expect(attributeAfterCall.y).toEqual(imageMeta.y); + expect(attributeAfterCall.width).toEqual(imageMeta.width); + expect(attributeAfterCall.height).toEqual(imageMeta.height); + }); + + it('should not override other properties', () => { + expect(attributeAfterCall.myProperty).toEqual('myProperty'); + }); + }); + + describe('updateDiscussionAvatarBadgeNumber', () => { + let discussionEl; + + beforeEach(() => { + discussionEl = document.createElement('div'); + discussionEl.innerHTML = ` + <a href="#" class="image-diff-avatar-link"> + <div class="badge"></div> + </a> + `; + domHelper.updateDiscussionAvatarBadgeNumber(discussionEl, badgeNumber); + }); + + it('should update avatar badge number', () => { + expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString()); + }); + }); + + describe('updateDiscussionBadgeNumber', () => { + let discussionEl; + + beforeEach(() => { + discussionEl = document.createElement('div'); + discussionEl.innerHTML = ` + <div class="badge"></div> + `; + domHelper.updateDiscussionBadgeNumber(discussionEl, badgeNumber); + }); + + it('should update discussion badge number', () => { + expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString()); + }); + }); + + describe('toggleCollapsed', () => { + let element; + let discussionNotesEl; + + beforeEach(() => { + element = document.createElement('div'); + element.innerHTML = ` + <div class="discussion-notes"> + <button></button> + <form class="discussion-form"></form> + </div> + `; + discussionNotesEl = element.querySelector('.discussion-notes'); + }); + + describe('not collapsed', () => { + beforeEach(() => { + domHelper.toggleCollapsed({ + currentTarget: element.querySelector('button'), + }); + }); + + it('should add collapsed class', () => { + expect(discussionNotesEl.classList.contains('collapsed')).toEqual(true); + }); + + it('should force formEl to display none', () => { + const formEl = element.querySelector('.discussion-form'); + expect(formEl.style.display).toEqual('none'); + }); + }); + + describe('collapsed', () => { + beforeEach(() => { + discussionNotesEl.classList.add('collapsed'); + + domHelper.toggleCollapsed({ + currentTarget: element.querySelector('button'), + }); + }); + + it('should remove collapsed class', () => { + expect(discussionNotesEl.classList.contains('collapsed')).toEqual(false); + }); + + it('should force formEl to display block', () => { + const formEl = element.querySelector('.discussion-form'); + expect(formEl.style.display).toEqual('block'); + }); + }); + }); +}); diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/javascripts/image_diff/helpers/utils_helper_spec.js new file mode 100644 index 00000000000..56d77a05c4c --- /dev/null +++ b/spec/javascripts/image_diff/helpers/utils_helper_spec.js @@ -0,0 +1,207 @@ +import * as utilsHelper from '~/image_diff/helpers/utils_helper'; +import ImageDiff from '~/image_diff/image_diff'; +import ReplacedImageDiff from '~/image_diff/replaced_image_diff'; +import ImageBadge from '~/image_diff/image_badge'; +import * as mockData from '../mock_data'; + +describe('utilsHelper', () => { + const { + noteId, + discussionId, + image, + imageProperties, + imageMeta, + } = mockData; + + describe('resizeCoordinatesToImageElement', () => { + let result; + + beforeEach(() => { + result = utilsHelper.resizeCoordinatesToImageElement(image, imageMeta); + }); + + it('should return x based on widthRatio', () => { + expect(result.x).toEqual(imageMeta.x * 0.5); + }); + + it('should return y based on heightRatio', () => { + expect(result.y).toEqual(imageMeta.y * 0.5); + }); + + it('should return image width', () => { + expect(result.width).toEqual(image.width); + }); + + it('should return image height', () => { + expect(result.height).toEqual(image.height); + }); + }); + + describe('generateBadgeFromDiscussionDOM', () => { + let discussionEl; + let result; + + beforeEach(() => { + const imageFrameEl = document.createElement('div'); + imageFrameEl.innerHTML = ` + <img src="${gl.TEST_HOST}/image.png"> + `; + discussionEl = document.createElement('div'); + discussionEl.dataset.discussionId = discussionId; + discussionEl.innerHTML = ` + <div class="note" id="${noteId}"></div> + `; + discussionEl.dataset.position = JSON.stringify(imageMeta); + result = utilsHelper.generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl); + }); + + it('should return actual image properties', () => { + const { actual } = result; + expect(actual.x).toEqual(imageMeta.x); + expect(actual.y).toEqual(imageMeta.y); + expect(actual.width).toEqual(imageMeta.width); + expect(actual.height).toEqual(imageMeta.height); + }); + + it('should return browser image properties', () => { + const { browser } = result; + expect(browser.x).toBeDefined(); + expect(browser.y).toBeDefined(); + expect(browser.width).toBeDefined(); + expect(browser.height).toBeDefined(); + }); + + it('should return instance of ImageBadge', () => { + expect(result instanceof ImageBadge).toEqual(true); + }); + + it('should return noteId', () => { + expect(result.noteId).toEqual(noteId); + }); + + it('should return discussionId', () => { + expect(result.discussionId).toEqual(discussionId); + }); + }); + + describe('getTargetSelection', () => { + let containerEl; + + beforeEach(() => { + containerEl = { + querySelector: () => imageProperties, + }; + }); + + function generateEvent(offsetX, offsetY) { + return { + currentTarget: containerEl, + offsetX, + offsetY, + }; + } + + it('should return browser properties', () => { + const event = generateEvent(25, 25); + const result = utilsHelper.getTargetSelection(event); + + const { browser } = result; + expect(browser.x).toEqual(event.offsetX); + expect(browser.y).toEqual(event.offsetY); + expect(browser.width).toEqual(imageProperties.width); + expect(browser.height).toEqual(imageProperties.height); + }); + + it('should return resized actual image properties', () => { + const event = generateEvent(50, 50); + const result = utilsHelper.getTargetSelection(event); + + const { actual } = result; + expect(actual.x).toEqual(100); + expect(actual.y).toEqual(100); + expect(actual.width).toEqual(imageProperties.naturalWidth); + expect(actual.height).toEqual(imageProperties.naturalHeight); + }); + + describe('normalize coordinates', () => { + it('should return x = 0 if x < 0', () => { + const event = generateEvent(-5, 50); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.x).toEqual(0); + }); + + it('should return x = width if x > width', () => { + const event = generateEvent(1000, 50); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.x).toEqual(imageProperties.width); + }); + + it('should return y = 0 if y < 0', () => { + const event = generateEvent(50, -10); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.y).toEqual(0); + }); + + it('should return y = height if y > height', () => { + const event = generateEvent(50, 1000); + const result = utilsHelper.getTargetSelection(event); + expect(result.browser.y).toEqual(imageProperties.height); + }); + }); + }); + + describe('initImageDiff', () => { + let glCache; + let fileEl; + + beforeEach(() => { + window.gl = window.gl || (window.gl = {}); + glCache = window.gl; + window.gl.ImageFile = () => {}; + fileEl = document.createElement('div'); + fileEl.innerHTML = ` + <div class="diff-file"></div> + `; + + spyOn(ImageDiff.prototype, 'init').and.callFake(() => {}); + spyOn(ReplacedImageDiff.prototype, 'init').and.callFake(() => {}); + }); + + afterEach(() => { + window.gl = glCache; + }); + + it('should initialize gl.ImageFile', () => { + spyOn(window.gl, 'ImageFile'); + + utilsHelper.initImageDiff(fileEl, false, false); + expect(gl.ImageFile).toHaveBeenCalled(); + }); + + it('should initialize ImageDiff if js-single-image', () => { + const diffFileEl = fileEl.querySelector('.diff-file'); + diffFileEl.innerHTML = ` + <div class="js-single-image"> + </div> + `; + + const imageDiff = utilsHelper.initImageDiff(fileEl, true, false); + expect(ImageDiff.prototype.init).toHaveBeenCalled(); + expect(imageDiff.canCreateNote).toEqual(true); + expect(imageDiff.renderCommentBadge).toEqual(false); + }); + + it('should initialize ReplacedImageDiff if js-replaced-image', () => { + const diffFileEl = fileEl.querySelector('.diff-file'); + diffFileEl.innerHTML = ` + <div class="js-replaced-image"> + </div> + `; + + const replacedImageDiff = utilsHelper.initImageDiff(fileEl, false, true); + expect(ReplacedImageDiff.prototype.init).toHaveBeenCalled(); + expect(replacedImageDiff.canCreateNote).toEqual(false); + expect(replacedImageDiff.renderCommentBadge).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/image_diff/image_badge_spec.js b/spec/javascripts/image_diff/image_badge_spec.js new file mode 100644 index 00000000000..87f98fc0926 --- /dev/null +++ b/spec/javascripts/image_diff/image_badge_spec.js @@ -0,0 +1,84 @@ +import ImageBadge from '~/image_diff/image_badge'; +import imageDiffHelper from '~/image_diff/helpers/index'; +import * as mockData from './mock_data'; + +describe('ImageBadge', () => { + const { noteId, discussionId, imageMeta } = mockData; + const options = { + noteId, + discussionId, + }; + + it('should save actual property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + actual: imageMeta, + })); + + const { actual } = imageBadge; + expect(actual.x).toEqual(imageMeta.x); + expect(actual.y).toEqual(imageMeta.y); + expect(actual.width).toEqual(imageMeta.width); + expect(actual.height).toEqual(imageMeta.height); + }); + + it('should save browser property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + browser: imageMeta, + })); + + const { browser } = imageBadge; + expect(browser.x).toEqual(imageMeta.x); + expect(browser.y).toEqual(imageMeta.y); + expect(browser.width).toEqual(imageMeta.width); + expect(browser.height).toEqual(imageMeta.height); + }); + + it('should save noteId', () => { + const imageBadge = new ImageBadge(options); + expect(imageBadge.noteId).toEqual(noteId); + }); + + it('should save discussionId', () => { + const imageBadge = new ImageBadge(options); + expect(imageBadge.discussionId).toEqual(discussionId); + }); + + describe('default values', () => { + let imageBadge; + + beforeEach(() => { + imageBadge = new ImageBadge(options); + }); + + it('should return defaultimageMeta if actual property is not provided', () => { + const { actual } = imageBadge; + expect(actual.x).toEqual(0); + expect(actual.y).toEqual(0); + expect(actual.width).toEqual(0); + expect(actual.height).toEqual(0); + }); + + it('should return defaultimageMeta if browser property is not provided', () => { + const { browser } = imageBadge; + expect(browser.x).toEqual(0); + expect(browser.y).toEqual(0); + expect(browser.width).toEqual(0); + expect(browser.height).toEqual(0); + }); + }); + + describe('imageEl property is provided and not browser property', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(true); + }); + + it('should generate browser property', () => { + const imageBadge = new ImageBadge(Object.assign({}, options, { + imageEl: document.createElement('img'), + })); + + expect(imageDiffHelper.resizeCoordinatesToImageElement).toHaveBeenCalled(); + expect(imageBadge.browser).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/javascripts/image_diff/image_diff_spec.js new file mode 100644 index 00000000000..346282328c7 --- /dev/null +++ b/spec/javascripts/image_diff/image_diff_spec.js @@ -0,0 +1,361 @@ +import ImageDiff from '~/image_diff/image_diff'; +import * as imageUtility from '~/lib/utils/image_utility'; +import imageDiffHelper from '~/image_diff/helpers/index'; +import * as mockData from './mock_data'; + +describe('ImageDiff', () => { + let element; + let imageDiff; + + beforeEach(() => { + setFixtures(` + <div id="element"> + <div class="diff-file"> + <div class="js-image-frame"> + <img src="${gl.TEST_HOST}/image.png"> + <div class="comment-indicator"></div> + <div id="badge-1" class="badge">1</div> + <div id="badge-2" class="badge">2</div> + <div id="badge-3" class="badge">3</div> + </div> + <div class="note-container"> + <div class="discussion-notes"> + <div class="js-diff-notes-toggle"></div> + <div class="notes"></div> + </div> + <div class="discussion-notes"> + <div class="js-diff-notes-toggle"></div> + <div class="notes"></div> + </div> + </div> + </div> + </div> + `); + element = document.getElementById('element'); + }); + + describe('constructor', () => { + beforeEach(() => { + imageDiff = new ImageDiff(element, { + canCreateNote: true, + renderCommentBadge: true, + }); + }); + + it('should set el', () => { + expect(imageDiff.el).toEqual(element); + }); + + it('should set canCreateNote', () => { + expect(imageDiff.canCreateNote).toEqual(true); + }); + + it('should set renderCommentBadge', () => { + expect(imageDiff.renderCommentBadge).toEqual(true); + }); + + it('should set $noteContainer', () => { + expect(imageDiff.$noteContainer[0]).toEqual(element.querySelector('.note-container')); + }); + + describe('default', () => { + beforeEach(() => { + imageDiff = new ImageDiff(element); + }); + + it('should set canCreateNote as false', () => { + expect(imageDiff.canCreateNote).toEqual(false); + }); + + it('should set renderCommentBadge as false', () => { + expect(imageDiff.renderCommentBadge).toEqual(false); + }); + }); + }); + + describe('init', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.init(); + }); + + it('should set imageFrameEl', () => { + expect(imageDiff.imageFrameEl).toEqual(element.querySelector('.diff-file .js-image-frame')); + }); + + it('should set imageEl', () => { + expect(imageDiff.imageEl).toEqual(element.querySelector('.diff-file .js-image-frame img')); + }); + + it('should call bindEvents', () => { + expect(imageDiff.bindEvents).toHaveBeenCalled(); + }); + }); + + describe('bindEvents', () => { + let imageEl; + + beforeEach(() => { + spyOn(imageDiffHelper, 'toggleCollapsed').and.callFake(() => {}); + spyOn(imageDiffHelper, 'commentIndicatorOnClick').and.callFake(() => {}); + spyOn(imageDiffHelper, 'removeCommentIndicator').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'imageClicked').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'addBadge').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'removeBadge').and.callFake(() => {}); + spyOn(ImageDiff.prototype, 'renderBadges').and.callFake(() => {}); + imageEl = element.querySelector('.diff-file .js-image-frame img'); + }); + + describe('default', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should register click event delegation to js-diff-notes-toggle', () => { + element.querySelector('.js-diff-notes-toggle').click(); + expect(imageDiffHelper.toggleCollapsed).toHaveBeenCalled(); + }); + + it('should register click event delegation to comment-indicator', () => { + element.querySelector('.comment-indicator').click(); + expect(imageDiffHelper.commentIndicatorOnClick).toHaveBeenCalled(); + }); + }); + + describe('image loaded', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(true); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + }); + + it('should renderBadges', () => {}); + }); + + describe('image not loaded', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should registers load eventListener', () => { + const loadEvent = new Event('load'); + imageEl.dispatchEvent(loadEvent); + expect(imageDiff.renderBadges).toHaveBeenCalled(); + }); + }); + + describe('canCreateNote', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element, { + canCreateNote: true, + }); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should register click.imageDiff event', () => { + const event = new CustomEvent('click.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.imageClicked).toHaveBeenCalled(); + }); + + it('should register blur.imageDiff event', () => { + const event = new CustomEvent('blur.imageDiff'); + element.dispatchEvent(event); + expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled(); + }); + + it('should register addBadge.imageDiff event', () => { + const event = new CustomEvent('addBadge.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.addBadge).toHaveBeenCalled(); + }); + + it('should register removeBadge.imageDiff event', () => { + const event = new CustomEvent('removeBadge.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.removeBadge).toHaveBeenCalled(); + }); + }); + + describe('canCreateNote is false', () => { + beforeEach(() => { + spyOn(imageUtility, 'isImageLoaded').and.returnValue(false); + imageDiff = new ImageDiff(element); + imageDiff.imageEl = imageEl; + imageDiff.bindEvents(); + }); + + it('should not register click.imageDiff event', () => { + const event = new CustomEvent('click.imageDiff'); + element.dispatchEvent(event); + expect(imageDiff.imageClicked).not.toHaveBeenCalled(); + }); + }); + }); + + describe('imageClicked', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'getTargetSelection').and.returnValue({ + actual: {}, + browser: {}, + }); + spyOn(imageDiffHelper, 'setPositionDataAttribute').and.callFake(() => {}); + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageClicked({ + detail: { + currentTarget: {}, + }, + }); + }); + + it('should call getTargetSelection', () => { + expect(imageDiffHelper.getTargetSelection).toHaveBeenCalled(); + }); + + it('should call setPositionDataAttribute', () => { + expect(imageDiffHelper.setPositionDataAttribute).toHaveBeenCalled(); + }); + + it('should call showCommentIndicator', () => { + expect(imageDiffHelper.showCommentIndicator).toHaveBeenCalled(); + }); + }); + + describe('renderBadges', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'renderBadge').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.renderBadges(); + }); + + it('should call renderBadge for each discussionEl', () => { + const discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes'); + expect(imageDiff.renderBadge.calls.count()).toEqual(discussionEls.length); + }); + }); + + describe('renderBadge', () => { + let discussionEls; + + beforeEach(() => { + spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'addImageCommentBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'generateBadgeFromDiscussionDOM').and.returnValue({ + browser: {}, + noteId: 'noteId', + }); + discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes'); + imageDiff = new ImageDiff(element); + imageDiff.renderBadge(discussionEls[0], 0); + }); + + it('should populate imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(1); + }); + + describe('renderCommentBadge', () => { + beforeEach(() => { + imageDiff.renderCommentBadge = true; + imageDiff.renderBadge(discussionEls[0], 0); + }); + + it('should call addImageCommentBadge', () => { + expect(imageDiffHelper.addImageCommentBadge).toHaveBeenCalled(); + }); + }); + + describe('renderCommentBadge is false', () => { + it('should call addImageBadge', () => { + expect(imageDiffHelper.addImageBadge).toHaveBeenCalled(); + }); + }); + }); + + describe('addBadge', () => { + beforeEach(() => { + spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'addAvatarBadge').and.callFake(() => {}); + spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame'); + imageDiff.addBadge({ + detail: { + x: 0, + y: 1, + width: 25, + height: 50, + noteId: 'noteId', + discussionId: 'discussionId', + }, + }); + }); + + it('should add imageBadge to imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(1); + }); + + it('should call addImageBadge', () => { + expect(imageDiffHelper.addImageBadge).toHaveBeenCalled(); + }); + + it('should call addAvatarBadge', () => { + expect(imageDiffHelper.addAvatarBadge).toHaveBeenCalled(); + }); + + it('should call updateDiscussionBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled(); + }); + }); + + describe('removeBadge', () => { + beforeEach(() => { + const { imageMeta } = mockData; + + spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {}); + spyOn(imageDiffHelper, 'updateDiscussionAvatarBadgeNumber').and.callFake(() => {}); + imageDiff = new ImageDiff(element); + imageDiff.imageBadges = [imageMeta, imageMeta, imageMeta]; + imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame'); + imageDiff.removeBadge({ + detail: { + badgeNumber: 2, + }, + }); + }); + + describe('cascade badge count', () => { + it('should update next imageBadgeEl value', () => { + const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.badge'); + expect(imageBadgeEls[0].innerText).toEqual('1'); + expect(imageBadgeEls[1].innerText).toEqual('2'); + expect(imageBadgeEls.length).toEqual(2); + }); + + it('should call updateDiscussionBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled(); + }); + + it('should call updateDiscussionAvatarBadgeNumber', () => { + expect(imageDiffHelper.updateDiscussionAvatarBadgeNumber).toHaveBeenCalled(); + }); + }); + + it('should remove badge from imageBadges', () => { + expect(imageDiff.imageBadges.length).toEqual(2); + }); + + it('should remove imageBadgeEl', () => { + expect(imageDiff.imageFrameEl.querySelector('#badge-2')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/image_diff/init_discussion_tab_spec.js b/spec/javascripts/image_diff/init_discussion_tab_spec.js new file mode 100644 index 00000000000..7c447d6f70d --- /dev/null +++ b/spec/javascripts/image_diff/init_discussion_tab_spec.js @@ -0,0 +1,37 @@ +import initDiscussionTab from '~/image_diff/init_discussion_tab'; +import imageDiffHelper from '~/image_diff/helpers/index'; + +describe('initDiscussionTab', () => { + beforeEach(() => { + setFixtures(` + <div class="timeline-content"> + <div class="diff-file js-image-file"></div> + <div class="diff-file js-image-file"></div> + </div> + `); + }); + + it('should pass canCreateNote as false to initImageDiff', (done) => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote) => { + expect(canCreateNote).toEqual(false); + done(); + }); + + initDiscussionTab(); + }); + + it('should pass renderCommentBadge as true to initImageDiff', (done) => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote, renderCommentBadge) => { + expect(renderCommentBadge).toEqual(true); + done(); + }); + + initDiscussionTab(); + }); + + it('should call initImageDiff for each diffFileEls', () => { + spyOn(imageDiffHelper, 'initImageDiff').and.callFake(() => {}); + initDiscussionTab(); + expect(imageDiffHelper.initImageDiff.calls.count()).toEqual(2); + }); +}); diff --git a/spec/javascripts/image_diff/mock_data.js b/spec/javascripts/image_diff/mock_data.js new file mode 100644 index 00000000000..a0d1732dd0a --- /dev/null +++ b/spec/javascripts/image_diff/mock_data.js @@ -0,0 +1,28 @@ +export const noteId = 'noteId'; +export const discussionId = 'discussionId'; +export const badgeText = 'badgeText'; +export const badgeNumber = 5; + +export const coordinate = { + x: 100, + y: 100, +}; + +export const image = { + width: 100, + height: 100, +}; + +export const imageProperties = { + width: image.width, + height: image.height, + naturalWidth: image.width * 2, + naturalHeight: image.height * 2, +}; + +export const imageMeta = { + x: coordinate.x, + y: coordinate.y, + width: imageProperties.naturalWidth, + height: imageProperties.naturalHeight, +}; diff --git a/spec/javascripts/image_diff/replaced_image_diff_spec.js b/spec/javascripts/image_diff/replaced_image_diff_spec.js new file mode 100644 index 00000000000..5f8cd7c531a --- /dev/null +++ b/spec/javascripts/image_diff/replaced_image_diff_spec.js @@ -0,0 +1,312 @@ +import ReplacedImageDiff from '~/image_diff/replaced_image_diff'; +import ImageDiff from '~/image_diff/image_diff'; +import { viewTypes } from '~/image_diff/view_types'; +import imageDiffHelper from '~/image_diff/helpers/index'; + +describe('ReplacedImageDiff', () => { + let element; + let replacedImageDiff; + + beforeEach(() => { + setFixtures(` + <div id="element"> + <div class="two-up"> + <div class="js-image-frame"> + <img src="${gl.TEST_HOST}/image.png"> + </div> + </div> + <div class="swipe"> + <div class="js-image-frame"> + <img src="${gl.TEST_HOST}/image.png"> + </div> + </div> + <div class="onion-skin"> + <div class="js-image-frame"> + <img src="${gl.TEST_HOST}/image.png"> + </div> + </div> + <div class="view-modes-menu"> + <div class="two-up">2-up</div> + <div class="swipe">Swipe</div> + <div class="onion-skin">Onion skin</div> + </div> + </div> + `); + element = document.getElementById('element'); + }); + + function setupImageFrameEls() { + replacedImageDiff.imageFrameEls = []; + replacedImageDiff.imageFrameEls[viewTypes.TWO_UP] = element.querySelector('.two-up .js-image-frame'); + replacedImageDiff.imageFrameEls[viewTypes.SWIPE] = element.querySelector('.swipe .js-image-frame'); + replacedImageDiff.imageFrameEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin .js-image-frame'); + } + + function setupViewModesEls() { + replacedImageDiff.viewModesEls = []; + replacedImageDiff.viewModesEls[viewTypes.TWO_UP] = element.querySelector('.view-modes-menu .two-up'); + replacedImageDiff.viewModesEls[viewTypes.SWIPE] = element.querySelector('.view-modes-menu .swipe'); + replacedImageDiff.viewModesEls[viewTypes.ONION_SKIN] = element.querySelector('.view-modes-menu .onion-skin'); + } + + function setupImageEls() { + replacedImageDiff.imageEls = []; + replacedImageDiff.imageEls[viewTypes.TWO_UP] = element.querySelector('.two-up img'); + replacedImageDiff.imageEls[viewTypes.SWIPE] = element.querySelector('.swipe img'); + replacedImageDiff.imageEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin img'); + } + + it('should extend ImageDiff', () => { + replacedImageDiff = new ReplacedImageDiff(element); + expect(replacedImageDiff instanceof ImageDiff).toEqual(true); + }); + + describe('init', () => { + beforeEach(() => { + spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(ReplacedImageDiff.prototype, 'generateImageEls').and.callFake(() => {}); + + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.init(); + }); + + it('should set imageFrameEls', () => { + const { imageFrameEls } = replacedImageDiff; + expect(imageFrameEls).toBeDefined(); + expect(imageFrameEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up .js-image-frame')); + expect(imageFrameEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe .js-image-frame')); + expect(imageFrameEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin .js-image-frame')); + }); + + it('should set viewModesEls', () => { + const { viewModesEls } = replacedImageDiff; + expect(viewModesEls).toBeDefined(); + expect(viewModesEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.view-modes-menu .two-up')); + expect(viewModesEls[viewTypes.SWIPE]).toEqual(element.querySelector('.view-modes-menu .swipe')); + expect(viewModesEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.view-modes-menu .onion-skin')); + }); + + it('should generateImageEls', () => { + expect(ReplacedImageDiff.prototype.generateImageEls).toHaveBeenCalled(); + }); + + it('should bindEvents', () => { + expect(ReplacedImageDiff.prototype.bindEvents).toHaveBeenCalled(); + }); + + describe('currentView', () => { + it('should set currentView', () => { + replacedImageDiff.init(viewTypes.ONION_SKIN); + expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN); + }); + + it('should default to viewTypes.TWO_UP', () => { + expect(replacedImageDiff.currentView).toEqual(viewTypes.TWO_UP); + }); + }); + }); + + describe('generateImageEls', () => { + beforeEach(() => { + spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + + replacedImageDiff = new ReplacedImageDiff(element, { + canCreateNote: false, + renderCommentBadge: false, + }); + + setupImageFrameEls(); + }); + + it('should set imageEls', () => { + replacedImageDiff.generateImageEls(); + const { imageEls } = replacedImageDiff; + expect(imageEls).toBeDefined(); + expect(imageEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up img')); + expect(imageEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe img')); + expect(imageEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin img')); + }); + }); + + describe('bindEvents', () => { + beforeEach(() => { + spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {}); + replacedImageDiff = new ReplacedImageDiff(element); + + setupViewModesEls(); + }); + + it('should call super.bindEvents', () => { + replacedImageDiff.bindEvents(); + expect(ImageDiff.prototype.bindEvents).toHaveBeenCalled(); + }); + + it('should register click eventlistener to 2-up view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.TWO_UP); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.TWO_UP].click(); + }); + + it('should register click eventlistener to swipe view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.SWIPE); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.SWIPE].click(); + }); + + it('should register click eventlistener to onion skin view mode', (done) => { + spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => { + expect(viewMode).toEqual(viewTypes.SWIPE); + done(); + }); + + replacedImageDiff.bindEvents(); + replacedImageDiff.viewModesEls[viewTypes.SWIPE].click(); + }); + }); + + describe('getters', () => { + describe('imageEl', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.currentView = viewTypes.TWO_UP; + setupImageEls(); + }); + + it('should return imageEl based on currentView', () => { + expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.two-up img')); + + replacedImageDiff.currentView = viewTypes.SWIPE; + expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.swipe img')); + }); + }); + + describe('imageFrameEl', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + replacedImageDiff.currentView = viewTypes.TWO_UP; + setupImageFrameEls(); + }); + + it('should return imageFrameEl based on currentView', () => { + expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.two-up .js-image-frame')); + + replacedImageDiff.currentView = viewTypes.ONION_SKIN; + expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.onion-skin .js-image-frame')); + }); + }); + }); + + describe('changeView', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + spyOn(imageDiffHelper, 'removeCommentIndicator').and.returnValue({ + removed: false, + }); + setupImageFrameEls(); + }); + + describe('invalid viewType', () => { + beforeEach(() => { + replacedImageDiff.changeView('some-view-name'); + }); + + it('should not call removeCommentIndicator', () => { + expect(imageDiffHelper.removeCommentIndicator).not.toHaveBeenCalled(); + }); + }); + + describe('valid viewType', () => { + beforeEach(() => { + jasmine.clock().install(); + spyOn(ReplacedImageDiff.prototype, 'renderNewView').and.callFake(() => {}); + replacedImageDiff.changeView(viewTypes.ONION_SKIN); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should call removeCommentIndicator', () => { + expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled(); + }); + + it('should update currentView to newView', () => { + expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN); + }); + + it('should clear imageBadges', () => { + expect(replacedImageDiff.imageBadges.length).toEqual(0); + }); + + it('should call renderNewView', () => { + jasmine.clock().tick(251); + expect(replacedImageDiff.renderNewView).toHaveBeenCalled(); + }); + }); + }); + + describe('renderNewView', () => { + beforeEach(() => { + replacedImageDiff = new ReplacedImageDiff(element); + }); + + it('should call renderBadges', () => { + spyOn(ReplacedImageDiff.prototype, 'renderBadges').and.callFake(() => {}); + + replacedImageDiff.renderNewView({ + removed: false, + }); + + expect(replacedImageDiff.renderBadges).toHaveBeenCalled(); + }); + + describe('removeIndicator', () => { + const indicator = { + removed: true, + x: 0, + y: 1, + image: { + width: 50, + height: 100, + }, + }; + + beforeEach(() => { + setupImageEls(); + setupImageFrameEls(); + }); + + it('should pass showCommentIndicator normalized indicator values', (done) => { + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {}); + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.callFake((imageEl, meta) => { + expect(meta.x).toEqual(indicator.x); + expect(meta.y).toEqual(indicator.y); + expect(meta.width).toEqual(indicator.image.width); + expect(meta.height).toEqual(indicator.image.height); + done(); + }); + replacedImageDiff.renderNewView(indicator); + }); + + it('should call showCommentIndicator', (done) => { + const normalized = { + normalized: true, + }; + spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(normalized); + spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake((imageFrameEl, normalizedIndicator) => { + expect(normalizedIndicator).toEqual(normalized); + done(); + }); + replacedImageDiff.renderNewView(indicator); + }); + }); + }); +}); diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/javascripts/image_diff/view_types_spec.js new file mode 100644 index 00000000000..e9639f46497 --- /dev/null +++ b/spec/javascripts/image_diff/view_types_spec.js @@ -0,0 +1,24 @@ +import { viewTypes, isValidViewType } from '~/image_diff/view_types'; + +describe('viewTypes', () => { + describe('isValidViewType', () => { + it('should return true for TWO_UP', () => { + expect(isValidViewType(viewTypes.TWO_UP)).toEqual(true); + }); + + it('should return true for SWIPE', () => { + expect(isValidViewType(viewTypes.SWIPE)).toEqual(true); + }); + + it('should return true for ONION_SKIN', () => { + expect(isValidViewType(viewTypes.ONION_SKIN)).toEqual(true); + }); + + it('should return false for non view types', () => { + expect(isValidViewType('some-view-type')).toEqual(false); + expect(isValidViewType(null)).toEqual(false); + expect(isValidViewType(undefined)).toEqual(false); + expect(isValidViewType('')).toEqual(false); + }); + }); +}); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 3daeb91b1e2..9033eb9ce02 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -138,9 +138,9 @@ describe('IntegrationSettingsForm', () => { deferred.resolve({ error: true, message: errorMessage, service_response: 'some error' }); const $flashContainer = $('.flash-container'); - expect($flashContainer.find('.flash-text').text()).toEqual('Test failed. some error'); + expect($flashContainer.find('.flash-text').text().trim()).toEqual('Test failed. some error'); expect($flashContainer.find('.flash-action')).toBeDefined(); - expect($flashContainer.find('.flash-action').text()).toEqual('Save anyway'); + expect($flashContainer.find('.flash-action').text().trim()).toEqual('Save anyway'); }); it('should submit form if ajax request responds without any error in test', () => { @@ -168,7 +168,7 @@ describe('IntegrationSettingsForm', () => { expect($flashAction).toBeDefined(); spyOn(integrationSettingsForm.$form, 'submit'); - $flashAction.trigger('click'); + $flashAction.get(0).click(); expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); }); @@ -181,7 +181,7 @@ describe('IntegrationSettingsForm', () => { deferred.reject(); - expect($('.flash-container .flash-text').text()).toEqual(errorMessage); + expect($('.flash-container .flash-text').text().trim()).toEqual(errorMessage); }); it('should always call `toggleSubmitBtnState` with `false` once request is completed', () => { diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js index 45f55395d3a..ceee08d47c5 100644 --- a/spec/javascripts/issuable_spec.js +++ b/spec/javascripts/issuable_spec.js @@ -1,80 +1,44 @@ -/* global IssuableIndex */ - -import '~/lib/utils/url_utility'; -import '~/issuable_index'; - -(() => { - const BASE_URL = '/user/project/issues?scope=all&state=closed'; - const DEFAULT_PARAMS = '&utf8=%E2%9C%93'; - - function updateForm(formValues, form) { - $.each(formValues, (id, value) => { - $(`#${id}`, form).val(value); - }); - } - - function resetForm(form) { - $('input[name!="utf8"]', form).each((index, input) => { - input.setAttribute('value', ''); +import IssuableIndex from '~/issuable_index'; + +describe('Issuable', () => { + let Issuable; + describe('initBulkUpdate', () => { + it('should not set bulkUpdateSidebar', () => { + Issuable = new IssuableIndex('issue_'); + expect(Issuable.bulkUpdateSidebar).not.toBeDefined(); }); - } - describe('Issuable', () => { - preloadFixtures('static/issuable_filter.html.raw'); + it('should set bulkUpdateSidebar', () => { + const element = document.createElement('div'); + element.classList.add('issues-bulk-update'); + document.body.appendChild(element); - beforeEach(() => { - loadFixtures('static/issuable_filter.html.raw'); - IssuableIndex.init(); - }); - - it('should be defined', () => { - expect(window.IssuableIndex).toBeDefined(); + Issuable = new IssuableIndex('issue_'); + expect(Issuable.bulkUpdateSidebar).toBeDefined(); }); + }); - describe('filtering', () => { - let $filtersForm; - - beforeEach(() => { - $filtersForm = $('.js-filter-form'); - loadFixtures('static/issuable_filter.html.raw'); - resetForm($filtersForm); - }); - - it('should contain only the default parameters', () => { - spyOn(gl.utils, 'visitUrl'); - - IssuableIndex.filterResults($filtersForm); - - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS); - }); - - it('should filter for the phrase "broken"', () => { - spyOn(gl.utils, 'visitUrl'); - - updateForm({ search: 'broken' }, $filtersForm); - IssuableIndex.filterResults($filtersForm); - const params = `${DEFAULT_PARAMS}&search=broken`; - - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); - }); - - it('should keep query parameters after modifying filter', () => { - spyOn(gl.utils, 'visitUrl'); + describe('resetIncomingEmailToken', () => { + beforeEach(() => { + const element = document.createElement('a'); + element.classList.add('incoming-email-token-reset'); + element.setAttribute('href', 'foo'); + document.body.appendChild(element); - // initial filter - updateForm({ milestone_title: 'v1.0' }, $filtersForm); + const input = document.createElement('input'); + input.setAttribute('id', 'issue_email'); + document.body.appendChild(input); - IssuableIndex.filterResults($filtersForm); - let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`; - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); + Issuable = new IssuableIndex('issue_'); + }); - // update filter - updateForm({ label_name: 'Frontend' }, $filtersForm); + it('should send request to reset email token', () => { + spyOn(jQuery, 'ajax').and.callThrough(); + document.querySelector('.incoming-email-token-reset').click(); - IssuableIndex.filterResults($filtersForm); - params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`; - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BASE_URL + params); - }); + expect(jQuery.ajax).toHaveBeenCalled(); + expect(jQuery.ajax.calls.argsFor(0)[0].url).toEqual('foo'); }); }); -})(); +}); + diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 583a3a74d77..2ea290108a4 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -332,4 +332,15 @@ describe('Issuable output', () => { .catch(done.fail); }); }); + + describe('show inline edit button', () => { + it('should not render by default', () => { + expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined(); + }); + + it('should render if showInlineEditButton', () => { + vm.showInlineEditButton = true; + expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined(); + }); + }); }); diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js index a2d90a9b9f5..c1edc785d0f 100644 --- a/spec/javascripts/issue_show/components/title_spec.js +++ b/spec/javascripts/issue_show/components/title_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import Store from '~/issue_show/stores'; import titleComponent from '~/issue_show/components/title.vue'; +import eventHub from '~/issue_show/event_hub'; describe('Title component', () => { let vm; @@ -25,7 +26,7 @@ describe('Title component', () => { it('renders title HTML', () => { expect( - vm.$el.innerHTML.trim(), + vm.$el.querySelector('.title').innerHTML.trim(), ).toBe('Testing <img>'); }); @@ -47,12 +48,12 @@ describe('Title component', () => { Vue.nextTick(() => { expect( - vm.$el.classList.contains('issue-realtime-pre-pulse'), + vm.$el.querySelector('.title').classList.contains('issue-realtime-pre-pulse'), ).toBeTruthy(); setTimeout(() => { expect( - vm.$el.classList.contains('issue-realtime-trigger-pulse'), + vm.$el.querySelector('.title').classList.contains('issue-realtime-trigger-pulse'), ).toBeTruthy(); done(); @@ -72,4 +73,36 @@ describe('Title component', () => { done(); }); }); + + describe('inline edit button', () => { + beforeEach(() => { + spyOn(eventHub, '$emit'); + }); + + it('should not show by default', () => { + expect(vm.$el.querySelector('.note-action-button')).toBeNull(); + }); + + it('should not show if canUpdate is false', () => { + vm.showInlineEditButton = true; + vm.canUpdate = false; + expect(vm.$el.querySelector('.note-action-button')).toBeNull(); + }); + + it('should show if showInlineEditButton and canUpdate', () => { + vm.showInlineEditButton = true; + vm.canUpdate = true; + expect(vm.$el.querySelector('.note-action-button')).toBeDefined(); + }); + + it('should trigger open.form event when clicked', () => { + vm.showInlineEditButton = true; + vm.canUpdate = true; + + Vue.nextTick(() => { + vm.$el.querySelector('.note-action-button').click(); + expect(eventHub.$emit).toHaveBeenCalledWith('open.form'); + }); + }); + }); }); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 60a452f2223..3636aac79a0 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,6 +1,5 @@ /* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */ import Issue from '~/issue'; -import CloseReopenReportToggle from '~/close_reopen_report_toggle'; import '~/lib/utils/text_utility'; describe('Issue', function() { @@ -189,37 +188,4 @@ describe('Issue', function() { }); }); }); - - describe('units', () => { - describe('class constructor', () => { - it('calls .initCloseReopenReport', () => { - spyOn(Issue.prototype, 'initCloseReopenReport'); - - new Issue(); // eslint-disable-line no-new - - expect(Issue.prototype.initCloseReopenReport).toHaveBeenCalled(); - }); - }); - - describe('initCloseReopenReport', () => { - it('calls .initDroplab', () => { - const container = jasmine.createSpyObj('container', ['querySelector']); - const dropdownTrigger = {}; - const dropdownList = {}; - const button = {}; - - spyOn(document, 'querySelector').and.returnValue(container); - spyOn(CloseReopenReportToggle.prototype, 'initDroplab'); - container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button); - - Issue.prototype.initCloseReopenReport(); - - expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button'); - expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled(); - }); - }); - }); }); diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/job_spec.js index 35149611095..5e67911d338 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/job_spec.js @@ -1,13 +1,11 @@ -/* eslint-disable no-new */ -/* global Build */ import { bytesToKiB } from '~/lib/utils/number_utils'; import '~/lib/utils/datetime_utility'; import '~/lib/utils/url_utility'; -import '~/build'; +import Job from '~/job'; import '~/breakpoints'; -describe('Build', () => { - const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; +describe('Job', () => { + const JOB_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1`; preloadFixtures('builds/build-with-artifacts.html.raw'); @@ -26,14 +24,14 @@ describe('Build', () => { describe('setup', () => { beforeEach(function () { - this.build = new Build(); + this.job = new Job(); }); it('copies build options', function () { - expect(this.build.pageUrl).toBe(BUILD_URL); - expect(this.build.buildStatus).toBe('success'); - expect(this.build.buildStage).toBe('test'); - expect(this.build.state).toBe(''); + expect(this.job.pageUrl).toBe(JOB_URL); + expect(this.job.buildStatus).toBe('success'); + expect(this.job.buildStage).toBe('test'); + expect(this.job.state).toBe(''); }); it('only shows the jobs matching the current stage', () => { @@ -87,15 +85,15 @@ describe('Build', () => { complete: true, }); - this.build = new Build(); + this.job = new Job(); expect($('#build-trace .js-build-output').text()).toMatch(/Update/); - expect(this.build.state).toBe('newstate'); + expect(this.job.state).toBe('newstate'); jasmine.clock().tick(4001); expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); - expect(this.build.state).toBe('finalstate'); + expect(this.job.state).toBe('finalstate'); }); it('replaces the entire build trace', () => { @@ -122,7 +120,7 @@ describe('Build', () => { append: false, }); - this.build = new Build(); + this.job = new Job(); expect($('#build-trace .js-build-output').text()).toMatch(/Update/); @@ -148,7 +146,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden'); }); @@ -167,7 +165,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-truncated-info-size').textContent.trim(), @@ -193,7 +191,7 @@ describe('Build', () => { deferred2.resolve(); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-truncated-info-size').textContent.trim(), @@ -227,7 +225,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect( document.querySelector('.js-raw-link').textContent.trim(), @@ -249,7 +247,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); expect(document.querySelector('.js-truncated-info').classList).toContain('hidden'); }); @@ -270,7 +268,7 @@ describe('Build', () => { total: 100, }); - this.build = new Build(); + this.job = new Job(); }); it('should render trace controls', () => { @@ -289,4 +287,19 @@ describe('Build', () => { }); }); }); + + describe('getBuildTrace', () => { + it('should request build trace with state parameter', (done) => { + spyOn(jQuery, 'ajax').and.callThrough(); + // eslint-disable-next-line no-new + new Job(); + + setTimeout(() => { + expect(jQuery.ajax).toHaveBeenCalledWith( + { url: `${JOB_URL}/trace.json`, data: { state: '' } }, + ); + done(); + }, 0); + }); + }); }); diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js index c7179b3e03d..4a210faa017 100644 --- a/spec/javascripts/jobs/header_spec.js +++ b/spec/javascripts/jobs/header_spec.js @@ -30,7 +30,6 @@ describe('Job details header', () => { email: 'foo@bar.com', avatar_url: 'link', }, - retry_path: 'path', new_issue_path: 'path', }, isLoading: false, @@ -49,12 +48,6 @@ describe('Job details header', () => { ).toEqual('failed Job #123 triggered 3 weeks ago by Foo'); }); - it('should render retry link', () => { - expect( - vm.$el.querySelector('.js-retry-button').getAttribute('href'), - ).toEqual(props.job.retry_path); - }); - it('should render new issue link', () => { expect( vm.$el.querySelector('.js-new-issue').getAttribute('href'), diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 17e4ef26b2c..43532275121 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -22,7 +22,7 @@ export default { details_path: '/root/ci-mock/-/jobs/4757', favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', action: { - icon: 'icon_action_retry', + icon: 'retry', title: 'Retry', path: '/root/ci-mock/-/jobs/4757/retry', method: 'post', diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index e47adc49224..a197b35f6fb 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -1,14 +1,12 @@ /* eslint-disable no-new */ -/* global IssuableContext */ -/* global LabelsSelect */ +import IssuableContext from '~/issuable_context'; +import LabelsSelect from '~/labels_select'; import '~/gl_dropdown'; import 'select2'; import '~/api'; import '~/create_label'; -import '~/issuable_context'; import '~/users_select'; -import '~/labels_select'; (() => { let saveLabelCount = 0; diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 787b405de47..a5298be5669 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -84,6 +84,62 @@ describe('common_utils', () => { expectGetElementIdToHaveBeenCalledWith('definição'); expectGetElementIdToHaveBeenCalledWith('user-content-definição'); }); + + it('scrolls element into view', () => { + document.body.innerHTML += ` + <div id="parent"> + <div style="height: 2000px;"></div> + <div id="test" style="height: 2000px;"></div> + </div> + `; + + window.history.pushState({}, null, '#test'); + commonUtils.handleLocationHash(); + + expectGetElementIdToHaveBeenCalledWith('test'); + expect(window.scrollY).toBe(document.getElementById('test').offsetTop); + + document.getElementById('parent').remove(); + }); + + it('scrolls user content element into view', () => { + document.body.innerHTML += ` + <div id="parent"> + <div style="height: 2000px;"></div> + <div id="user-content-test" style="height: 2000px;"></div> + </div> + `; + + window.history.pushState({}, null, '#test'); + commonUtils.handleLocationHash(); + + expectGetElementIdToHaveBeenCalledWith('test'); + expectGetElementIdToHaveBeenCalledWith('user-content-test'); + expect(window.scrollY).toBe(document.getElementById('user-content-test').offsetTop); + + document.getElementById('parent').remove(); + }); + + it('scrolls to element with offset from navbar', () => { + spyOn(window, 'scrollBy').and.callThrough(); + document.body.innerHTML += ` + <div id="parent"> + <div class="navbar-gitlab" style="position: fixed; top: 0; height: 50px;"></div> + <div style="height: 2000px; margin-top: 50px;"></div> + <div id="user-content-test" style="height: 2000px;"></div> + </div> + `; + + window.history.pushState({}, null, '#test'); + commonUtils.handleLocationHash(); + + expectGetElementIdToHaveBeenCalledWith('test'); + expectGetElementIdToHaveBeenCalledWith('user-content-test'); + expect(window.scrollY).toBe(document.getElementById('user-content-test').offsetTop - 50); + expect(window.scrollBy).toHaveBeenCalledWith(0, -50); + + document.getElementById('parent').remove(); + }); }); describe('setParamInURL', () => { @@ -411,15 +467,27 @@ describe('common_utils', () => { commonUtils.ajaxPost(requestURL, data); expect(ajaxSpy.calls.allArgs()[0][0].type).toEqual('POST'); }); + }); - describe('gl.utils.spriteIcon', () => { - beforeEach(() => { - window.gon.sprite_icons = 'icons.svg'; - }); + describe('spriteIcon', () => { + let beforeGon; - it('should return the svg for a linked icon', () => { - expect(gl.utils.spriteIcon('test')).toEqual('<svg><use xlink:href="icons.svg#test" /></svg>'); - }); + beforeEach(() => { + window.gon = window.gon || {}; + beforeGon = Object.assign({}, window.gon); + window.gon.sprite_icons = 'icons.svg'; + }); + + afterEach(() => { + window.gon = beforeGon; + }); + + it('should return the svg for a linked icon', () => { + expect(commonUtils.spriteIcon('test')).toEqual('<svg ><use xlink:href="icons.svg#test" /></svg>'); + }); + + it('should set svg className when passed', () => { + expect(commonUtils.spriteIcon('test', 'fa fa-test')).toEqual('<svg class="fa fa-test"><use xlink:href="icons.svg#test" /></svg>'); }); }); }); diff --git a/spec/javascripts/lib/utils/datefix_spec.js b/spec/javascripts/lib/utils/datefix_spec.js new file mode 100644 index 00000000000..e58ac4300ba --- /dev/null +++ b/spec/javascripts/lib/utils/datefix_spec.js @@ -0,0 +1,27 @@ +import { pad, pikadayToString } from '~/lib/utils/datefix'; + +describe('datefix', () => { + describe('pad', () => { + it('should add a 0 when length is smaller than 2', () => { + expect(pad(2)).toEqual('02'); + }); + + it('should not add a zero when lenght matches the default', () => { + expect(pad(12)).toEqual('12'); + }); + + it('should add a 0 when lenght is smaller than the provided', () => { + expect(pad(12, 3)).toEqual('012'); + }); + }); + + describe('parsePikadayDate', () => { + // removed because of https://gitlab.com/gitlab-org/gitlab-ce/issues/39834 + }); + + describe('pikadayToString', () => { + it('should format a UTC date into yyyy-mm-dd format', () => { + expect(pikadayToString(new Date('2020-01-29'))).toEqual('2020-01-29'); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/javascripts/lib/utils/image_utility_spec.js new file mode 100644 index 00000000000..75addfcc833 --- /dev/null +++ b/spec/javascripts/lib/utils/image_utility_spec.js @@ -0,0 +1,32 @@ +import * as imageUtility from '~/lib/utils/image_utility'; + +describe('imageUtility', () => { + describe('isImageLoaded', () => { + it('should return false when image.complete is false', () => { + const element = { + complete: false, + naturalHeight: 100, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(false); + }); + + it('should return false when naturalHeight = 0', () => { + const element = { + complete: true, + naturalHeight: 0, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(false); + }); + + it('should return true when image.complete and naturalHeight != 0', () => { + const element = { + complete: true, + naturalHeight: 100, + }; + + expect(imageUtility.isImageLoaded(element)).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/sticky_spec.js b/spec/javascripts/lib/utils/sticky_spec.js index c3ee3ef9825..b87c836654d 100644 --- a/spec/javascripts/lib/utils/sticky_spec.js +++ b/spec/javascripts/lib/utils/sticky_spec.js @@ -1,52 +1,79 @@ import { isSticky } from '~/lib/utils/sticky'; describe('sticky', () => { - const el = { - offsetTop: 0, - classList: {}, - }; + let el; beforeEach(() => { - el.offsetTop = 0; - el.classList.add = jasmine.createSpy('spy'); - el.classList.remove = jasmine.createSpy('spy'); + document.body.innerHTML += ` + <div class="parent"> + <div id="js-sticky"></div> + </div> + `; + + el = document.getElementById('js-sticky'); }); - describe('classList.remove', () => { - it('does not call classList.remove when stuck', () => { - isSticky(el, 0, 0); + afterEach(() => { + el.parentNode.remove(); + }); + + describe('when stuck', () => { + it('does not remove is-stuck class', () => { + isSticky(el, 0, el.offsetTop); + isSticky(el, 0, el.offsetTop); expect( - el.classList.remove, - ).not.toHaveBeenCalled(); + el.classList.contains('is-stuck'), + ).toBeTruthy(); }); - it('calls classList.remove when not stuck', () => { - el.offsetTop = 10; - isSticky(el, 0, 0); + it('adds is-stuck class', () => { + isSticky(el, 0, el.offsetTop); expect( - el.classList.remove, - ).toHaveBeenCalledWith('is-stuck'); + el.classList.contains('is-stuck'), + ).toBeTruthy(); + }); + + it('inserts placeholder element', () => { + isSticky(el, 0, el.offsetTop, true); + + expect( + document.querySelector('.sticky-placeholder'), + ).not.toBeNull(); }); }); - describe('classList.add', () => { - it('calls classList.add when stuck', () => { + describe('when not stuck', () => { + it('removes is-stuck class', () => { + spyOn(el.classList, 'remove').and.callThrough(); + + isSticky(el, 0, el.offsetTop); isSticky(el, 0, 0); expect( - el.classList.add, + el.classList.remove, ).toHaveBeenCalledWith('is-stuck'); + expect( + el.classList.contains('is-stuck'), + ).toBeFalsy(); }); - it('does not call classList.add when not stuck', () => { - el.offsetTop = 10; + it('does not add is-stuck class', () => { isSticky(el, 0, 0); expect( - el.classList.add, - ).not.toHaveBeenCalled(); + el.classList.contains('is-stuck'), + ).toBeFalsy(); + }); + + it('removes placeholder', () => { + isSticky(el, 0, el.offsetTop, true); + isSticky(el, 0, 0, true); + + expect( + document.querySelector('.sticky-placeholder'), + ).toBeNull(); }); }); }); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index f1a975ba962..829b3ef5735 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -1,4 +1,4 @@ -import '~/lib/utils/text_utility'; +import { highCountTrim } from '~/lib/utils/text_utility'; describe('text_utility', () => { describe('gl.text.getTextWidth', () => { @@ -35,14 +35,14 @@ describe('text_utility', () => { }); }); - describe('gl.text.highCountTrim', () => { + describe('highCountTrim', () => { it('returns 99+ for count >= 100', () => { - expect(gl.text.highCountTrim(105)).toBe('99+'); - expect(gl.text.highCountTrim(100)).toBe('99+'); + expect(highCountTrim(105)).toBe('99+'); + expect(highCountTrim(100)).toBe('99+'); }); it('returns exact number for count < 100', () => { - expect(gl.text.highCountTrim(45)).toBe(45); + expect(highCountTrim(45)).toBe(45); }); }); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index aee274641e8..645664a5219 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -18,19 +18,25 @@ import '~/line_highlighter'; beforeEach(function() { loadFixtures('static/line_highlighter.html.raw'); this["class"] = new LineHighlighter(); - this.css = this["class"].highlightClass; + this.css = this["class"].highlightLineClass; return this.spies = { __setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {}) }; }); describe('behavior', function() { it('highlights one line given in the URL hash', function() { - new LineHighlighter('#L13'); + new LineHighlighter({ hash: '#L13' }); return expect($('#LC13')).toHaveClass(this.css); }); + it('highlights one line given in the URL hash with given CSS class name', function() { + const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' }); + expect(hiliter.highlightLineClass).toBe('hilite'); + expect($('#LC13')).toHaveClass('hilite'); + expect($('#LC13')).not.toHaveClass('hll'); + }); it('highlights a range of lines given in the URL hash', function() { var line, results; - new LineHighlighter('#L5-25'); + new LineHighlighter({ hash: '#L5-25' }); expect($("." + this.css).length).toBe(21); results = []; for (line = 5; line <= 25; line += 1) { @@ -41,7 +47,7 @@ import '~/line_highlighter'; it('scrolls to the first highlighted line on initial load', function() { var spy; spy = spyOn($, 'scrollTo'); - new LineHighlighter('#L5-25'); + new LineHighlighter({ hash: '#L5-25' }); return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything()); }); it('discards click events', function() { @@ -50,10 +56,10 @@ import '~/line_highlighter'; clickLine(13); return expect(spy).toHaveBeenPrevented(); }); - return it('handles garbage input from the hash', function() { + it('handles garbage input from the hash', function() { var func; func = function() { - return new LineHighlighter('#blob-content-holder'); + return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' }); }; return expect(func).not.toThrow(); }); diff --git a/spec/javascripts/locale/sprintf_spec.js b/spec/javascripts/locale/sprintf_spec.js new file mode 100644 index 00000000000..52e903b819f --- /dev/null +++ b/spec/javascripts/locale/sprintf_spec.js @@ -0,0 +1,74 @@ +import sprintf from '~/locale/sprintf'; + +describe('locale', () => { + describe('sprintf', () => { + it('does not modify string without parameters', () => { + const input = 'No parameters'; + + const output = sprintf(input); + + expect(output).toBe(input); + }); + + it('ignores extraneous parameters', () => { + const input = 'No parameters'; + + const output = sprintf(input, { ignore: 'this' }); + + expect(output).toBe(input); + }); + + it('ignores extraneous placeholders', () => { + const input = 'No %{parameters}'; + + const output = sprintf(input); + + expect(output).toBe(input); + }); + + it('replaces parameters', () => { + const input = '%{name} has %{count} parameters'; + const parameters = { + name: 'this', + count: 2, + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('this has 2 parameters'); + }); + + it('replaces multiple occurrences', () => { + const input = 'to %{verb} or not to %{verb}'; + const parameters = { + verb: 'be', + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('to be or not to be'); + }); + + it('escapes parameters', () => { + const input = 'contains %{userContent}'; + const parameters = { + userContent: '<script>alert("malicious!")</script>', + }; + + const output = sprintf(input, parameters); + + expect(output).toBe('contains <script>alert("malicious!")</script>'); + }); + + it('does not escape parameters for escapeParameters = false', () => { + const input = 'contains %{safeContent}'; + const parameters = { + safeContent: '<strong>bold attempt</strong>', + }; + + const output = sprintf(input, parameters, false); + + expect(output).toBe('contains <strong>bold attempt</strong>'); + }); + }); +}); diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js index 395dc560671..6054b75d0b8 100644 --- a/spec/javascripts/merge_request_notes_spec.js +++ b/spec/javascripts/merge_request_notes_spec.js @@ -1,6 +1,6 @@ /* global Notes */ -import 'vendor/autosize'; +import 'autosize'; import '~/gl_form'; import '~/lib/utils/text_utility'; import '~/render_gfm'; @@ -23,12 +23,17 @@ describe('Merge request notes', () => { loadFixtures(discussionTabFixture); gl.utils.disableButtonIfEmptyField = _.noop; window.project_uploads_path = 'http://test.host/uploads'; - $('body').data('page', 'projects:merge_requests:show'); + $('body').attr('data-page', 'projects:merge_requests:show'); window.gon.current_user_id = $('.note:last').data('author-id'); return new Notes('', []); }); + afterEach(() => { + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); + }); + describe('up arrow', () => { it('edits last comment when triggered in main form', () => { const upArrowEvent = $.Event('keydown'); @@ -71,12 +76,17 @@ describe('Merge request notes', () => { <textarea class="js-note-text"></textarea> </form>`; setFixtures(diffsResponse.html + noteFormHtml); - $('body').data('page', 'projects:merge_requests:show'); + $('body').attr('data-page', 'projects:merge_requests:show'); window.gon.current_user_id = $('.note:last').data('author-id'); return new Notes('', []); }); + afterEach(() => { + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); + }); + describe('up arrow', () => { it('edits last comment in discussion when triggered in discussion form', (done) => { const upArrowEvent = $.Event('keydown'); diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 6ff42e2378d..3ab901da6b6 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -58,5 +58,44 @@ import IssuablesHelper from '~/helpers/issuables_helper'; expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled(); }); }); + + describe('hideCloseButton', () => { + describe('merge request of another user', () => { + beforeEach(() => { + loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + this.el = document.querySelector('.merge-request .issuable-actions'); + const merge = new MergeRequest(); + merge.hideCloseButton(); + }); + + it('hides the dropdown close item and selects the next item', () => { + const closeItem = this.el.querySelector('li.close-item'); + const smallCloseItem = this.el.querySelector('.js-close-item'); + const reportItem = this.el.querySelector('li.report-item'); + + expect(closeItem).toHaveClass('hidden'); + expect(smallCloseItem).toHaveClass('hidden'); + expect(reportItem).toHaveClass('droplab-item-selected'); + expect(reportItem).not.toHaveClass('hidden'); + }); + }); + + describe('merge request of current_user', () => { + beforeEach(() => { + loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + this.el = document.querySelector('.merge-request .issuable-actions'); + const merge = new MergeRequest(); + merge.hideCloseButton(); + }); + + it('hides the close button', () => { + const closeButton = this.el.querySelector('.btn-close'); + const smallCloseItem = this.el.querySelector('.js-close-item'); + + expect(closeButton).toHaveClass('hidden'); + expect(smallCloseItem).toHaveClass('hidden'); + }); + }); + }); }); }).call(window); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index eadab738376..e441d1153ed 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -5,8 +5,7 @@ import '~/merge_request_tabs'; import '~/commit/pipelines/pipelines_bundle'; import '~/breakpoints'; import '~/lib/utils/common_utils'; -import '~/diff'; -import '~/files_comment_button'; +import Diff from '~/diff'; import '~/notes'; import 'vendor/jquery.scrollTo'; @@ -225,7 +224,7 @@ import 'vendor/jquery.scrollTo'; describe('with "Side-by-side"/parallel diff view', () => { beforeEach(function () { this.class.diffViewType = () => 'parallel'; - gl.Diff.prototype.diffViewType = () => 'parallel'; + Diff.prototype.diffViewType = () => 'parallel'; }); it('maintains `container-limited` for pipelines tab', function (done) { @@ -277,7 +276,7 @@ import 'vendor/jquery.scrollTo'; describe('loadDiff', function () { beforeEach(() => { loadFixtures('merge_requests/diff_comment.html.raw'); - spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests'); + $('body').attr('data-page', 'projects:merge_requests:show'); window.gl.ImageFile = () => {}; window.notes = new Notes('', []); spyOn(window.notes, 'toggleDiffNote').and.callThrough(); @@ -286,6 +285,9 @@ import 'vendor/jquery.scrollTo'; afterEach(() => { delete window.gl.ImageFile; delete window.notes; + + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); }); it('requires an absolute pathname', function () { @@ -416,5 +418,28 @@ import 'vendor/jquery.scrollTo'; }); }); }); + + describe('expandViewContainer', function () { + beforeEach(() => { + $('body').append('<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>'); + }); + + afterEach(() => { + $('.content-wrapper').remove(); + }); + + it('removes container-limited from containers', function () { + this.class.expandViewContainer(); + + expect($('.content-wrapper')).not.toContainElement('.container-limited'); + }); + + it('does remove container-limited from breadcrumbs', function () { + $('.container-limited').addClass('breadcrumbs'); + this.class.expandViewContainer(); + + expect($('.content-wrapper')).toContainElement('.container-limited'); + }); + }); }); }).call(window); diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js index c2ff38ffab9..dea42d755d4 100644 --- a/spec/javascripts/monitoring/graph/deployment_spec.js +++ b/spec/javascripts/monitoring/graph/deployment_spec.js @@ -21,6 +21,7 @@ describe('MonitoringDeployment', () => { const component = createComponent({ showDeployInfo: false, deploymentData: reducedDeploymentData, + graphWidth: 440, graphHeight: 300, graphHeightOffset: 120, }); @@ -36,6 +37,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: false, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -49,6 +51,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: false, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -62,6 +65,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: false, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -75,6 +79,7 @@ describe('MonitoringDeployment', () => { const component = createComponent({ showDeployInfo: true, deploymentData: reducedDeploymentData, + graphWidth: 440, graphHeight: 300, graphHeightOffset: 120, }); @@ -82,12 +87,29 @@ describe('MonitoringDeployment', () => { expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull(); }); + it('positions the flag to the left when the xPos is too far right', () => { + reducedDeploymentData[0].showDeploymentFlag = false; + reducedDeploymentData[0].xPos = 250; + const component = createComponent({ + showDeployInfo: true, + deploymentData: reducedDeploymentData, + graphWidth: 440, + graphHeight: 300, + graphHeightOffset: 120, + }); + + expect( + component.positionFlag(reducedDeploymentData[0]), + ).toBeLessThan(0); + }); + it('shows the deployment flag', () => { reducedDeploymentData[0].showDeploymentFlag = true; const component = createComponent({ showDeployInfo: true, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -102,6 +124,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: true, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -115,6 +138,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: true, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); @@ -127,6 +151,7 @@ describe('MonitoringDeployment', () => { showDeployInfo: true, deploymentData: reducedDeploymentData, graphHeight: 300, + graphWidth: 440, graphHeightOffset: 120, }); diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index 14794cbfd50..8ee1171419d 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -14,19 +14,22 @@ function getCoordinate(component, selector, coordinate) { return parseInt(coordinateVal, 10); } +const defaultValuesComponent = { + currentXCoordinate: 200, + currentYCoordinate: 100, + currentFlagPosition: 100, + currentData: { + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }, + graphHeight: 300, + graphHeightOffset: 120, + showFlagContent: true, +}; + describe('GraphFlag', () => { it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => { - const component = createComponent({ - currentXCoordinate: 200, - currentYCoordinate: 100, - currentFlagPosition: 100, - currentData: { - time: new Date('2017-06-04T18:17:33.501Z'), - value: '1.49609375', - }, - graphHeight: 300, - graphHeightOffset: 120, - }); + const component = createComponent(defaultValuesComponent); expect(getCoordinate(component, '.selected-metric-line', 'x1')) .toEqual(component.currentXCoordinate); @@ -35,17 +38,7 @@ describe('GraphFlag', () => { }); it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { - const component = createComponent({ - currentXCoordinate: 200, - currentYCoordinate: 100, - currentFlagPosition: 100, - currentData: { - time: new Date('2017-06-04T18:17:33.501Z'), - value: '1.49609375', - }, - graphHeight: 300, - graphHeightOffset: 120, - }); + const component = createComponent(defaultValuesComponent); const svg = component.$el.querySelector('.rect-text-metric'); expect(svg.tagName).toEqual('svg'); @@ -54,17 +47,7 @@ describe('GraphFlag', () => { describe('Computed props', () => { it('calculatedHeight', () => { - const component = createComponent({ - currentXCoordinate: 200, - currentYCoordinate: 100, - currentFlagPosition: 100, - currentData: { - time: new Date('2017-06-04T18:17:33.501Z'), - value: '1.49609375', - }, - graphHeight: 300, - graphHeightOffset: 120, - }); + const component = createComponent(defaultValuesComponent); expect(component.calculatedHeight).toEqual(180); }); diff --git a/spec/javascripts/monitoring/graph_path_spec.js b/spec/javascripts/monitoring/graph_path_spec.js index a4844636d09..81825a3ae87 100644 --- a/spec/javascripts/monitoring/graph_path_spec.js +++ b/spec/javascripts/monitoring/graph_path_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import GraphPath from '~/monitoring/components/graph_path.vue'; +import GraphPath from '~/monitoring/components/graph/path.vue'; import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data'; diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js index 7d8b0744af1..fd79abe241a 100644 --- a/spec/javascripts/monitoring/graph_spec.js +++ b/spec/javascripts/monitoring/graph_spec.js @@ -44,7 +44,7 @@ describe('Graph', () => { .not.toEqual(-1); }); - it('outterViewBox gets a width and height property based on the DOM size of the element', () => { + it('outerViewBox gets a width and height property based on the DOM size of the element', () => { const component = createComponent({ graphData: convertedMetrics[1], classType: 'col-md-6', @@ -52,8 +52,8 @@ describe('Graph', () => { deploymentData, }); - const viewBoxArray = component.outterViewBox.split(' '); - expect(typeof component.outterViewBox).toEqual('string'); + const viewBoxArray = component.outerViewBox.split(' '); + expect(typeof component.outerViewBox).toEqual('string'); expect(viewBoxArray[2]).toEqual(component.graphWidth.toString()); expect(viewBoxArray[3]).toEqual(component.graphHeight.toString()); }); @@ -86,4 +86,22 @@ describe('Graph', () => { expect(component.yAxisLabel).toEqual(component.graphData.y_label); expect(component.legendTitle).toEqual(component.graphData.queries[0].label); }); + + it('sets the currentData object based on the hovered data index', () => { + const component = createComponent({ + graphData: convertedMetrics[1], + classType: 'col-md-6', + updateAspectRatio: false, + deploymentData, + graphIdentifier: 0, + hoverData: { + hoveredDate: new Date('Sun Aug 27 2017 06:11:51 GMT-0500 (CDT)'), + currentDeployXPos: null, + }, + }); + + component.positionFlag(); + expect(component.currentData).toBe(component.timeSeries[0].values[10]); + expect(component.currentDataIndex).toEqual(10); + }); }); diff --git a/spec/javascripts/namespace_select_spec.js b/spec/javascripts/namespace_select_spec.js new file mode 100644 index 00000000000..9d7625ca269 --- /dev/null +++ b/spec/javascripts/namespace_select_spec.js @@ -0,0 +1,65 @@ +import NamespaceSelect from '~/namespace_select'; + +describe('NamespaceSelect', () => { + beforeEach(() => { + spyOn($.fn, 'glDropdown'); + }); + + it('initializes glDropdown', () => { + const dropdown = document.createElement('div'); + + // eslint-disable-next-line no-new + new NamespaceSelect({ dropdown }); + + expect($.fn.glDropdown).toHaveBeenCalled(); + }); + + describe('as input', () => { + let glDropdownOptions; + + beforeEach(() => { + const dropdown = document.createElement('div'); + // eslint-disable-next-line no-new + new NamespaceSelect({ dropdown }); + glDropdownOptions = $.fn.glDropdown.calls.argsFor(0)[0]; + }); + + it('prevents click events', () => { + const dummyEvent = new Event('dummy'); + spyOn(dummyEvent, 'preventDefault'); + + glDropdownOptions.clicked({ e: dummyEvent }); + + expect(dummyEvent.preventDefault).toHaveBeenCalled(); + }); + }); + + describe('as filter', () => { + let glDropdownOptions; + + beforeEach(() => { + const dropdown = document.createElement('div'); + dropdown.dataset.isFilter = 'true'; + // eslint-disable-next-line no-new + new NamespaceSelect({ dropdown }); + glDropdownOptions = $.fn.glDropdown.calls.argsFor(0)[0]; + }); + + it('does not prevent click events', () => { + const dummyEvent = new Event('dummy'); + spyOn(dummyEvent, 'preventDefault'); + + glDropdownOptions.clicked({ e: dummyEvent }); + + expect(dummyEvent.preventDefault).not.toHaveBeenCalled(); + }); + + it('sets URL of dropdown items', () => { + const dummyNamespace = { id: 'eal' }; + + const itemUrl = glDropdownOptions.url(dummyNamespace); + + expect(itemUrl).toContain(`namespace_id=${dummyNamespace.id}`); + }); + }); +}); diff --git a/spec/javascripts/notes/components/issue_comment_form_spec.js b/spec/javascripts/notes/components/issue_comment_form_spec.js index 1c8b1b98242..a26fc8f63cc 100644 --- a/spec/javascripts/notes/components/issue_comment_form_spec.js +++ b/spec/javascripts/notes/components/issue_comment_form_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import autosize from 'vendor/autosize'; +import Autosize from 'autosize'; import store from '~/notes/stores'; import issueCommentForm from '~/notes/components/issue_comment_form.vue'; import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data'; @@ -33,6 +33,30 @@ describe('issue_comment_form component', () => { expect(vm.$el.querySelector('.timeline-icon .user-avatar-link').getAttribute('href')).toEqual(userDataMock.path); }); + describe('handleSave', () => { + it('should request to save note when note is entered', () => { + vm.note = 'hello world'; + spyOn(vm, 'saveNote').and.returnValue(new Promise(() => {})); + spyOn(vm, 'resizeTextarea'); + spyOn(vm, 'stopPolling'); + + vm.handleSave(); + expect(vm.isSubmitting).toEqual(true); + expect(vm.note).toEqual(''); + expect(vm.saveNote).toHaveBeenCalled(); + expect(vm.stopPolling).toHaveBeenCalled(); + expect(vm.resizeTextarea).toHaveBeenCalled(); + }); + + it('should toggle issue state when no note', () => { + spyOn(vm, 'toggleIssueState'); + + vm.handleSave(); + + expect(vm.toggleIssueState).toHaveBeenCalled(); + }); + }); + describe('textarea', () => { it('should render textarea with placeholder', () => { expect( @@ -40,6 +64,22 @@ describe('issue_comment_form component', () => { ).toEqual('Write a comment or drag your files here...'); }); + it('should make textarea disabled while requesting', (done) => { + const $submitButton = $(vm.$el.querySelector('.js-comment-submit-button')); + vm.note = 'hello world'; + spyOn(vm, 'stopPolling'); + spyOn(vm, 'saveNote').and.returnValue(new Promise(() => {})); + + vm.$nextTick(() => { // Wait for vm.note change triggered. It should enable $submitButton. + $submitButton.trigger('click'); + + vm.$nextTick(() => { // Wait for vm.isSubmitting triggered. It should disable textarea. + expect(vm.$el.querySelector('.js-main-target-form textarea').disabled).toBeTruthy(); + done(); + }); + }); + }); + it('should support quick actions', () => { expect( vm.$el.querySelector('.js-main-target-form textarea').getAttribute('data-supports-quick-actions'), @@ -57,14 +97,14 @@ describe('issue_comment_form component', () => { }); it('should resize textarea after note discarded', (done) => { - spyOn(autosize, 'update'); + spyOn(Autosize, 'update'); spyOn(vm, 'discard').and.callThrough(); vm.note = 'foo'; vm.discard(); Vue.nextTick(() => { - expect(autosize.update).toHaveBeenCalled(); + expect(Autosize.update).toHaveBeenCalled(); done(); }); }); diff --git a/spec/javascripts/notes/components/issue_placeholder_system_note_spec.js b/spec/javascripts/notes/components/issue_placeholder_system_note_spec.js deleted file mode 100644 index d508a49f710..00000000000 --- a/spec/javascripts/notes/components/issue_placeholder_system_note_spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import Vue from 'vue'; -import placeholderSystemNote from '~/notes/components/issue_placeholder_system_note.vue'; - -describe('issue placeholder system note component', () => { - let mountComponent; - beforeEach(() => { - const PlaceholderSystemNote = Vue.extend(placeholderSystemNote); - - mountComponent = props => new PlaceholderSystemNote({ - propsData: { - note: { - body: props, - }, - }, - }).$mount(); - }); - - it('should render system note placeholder with plain text', () => { - const vm = mountComponent('This is a placeholder'); - - expect(vm.$el.tagName).toEqual('LI'); - expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual('This is a placeholder'); - }); -}); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 2b2219dcf0c..3d1ca870ca4 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -1,5 +1,5 @@ import * as actions from '~/notes/stores/actions'; -import testAction from './helpers'; +import testAction from '../../helpers/vuex_action_helper'; import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data'; describe('Actions Notes Store', () => { diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 3e791a31604..928a4b461cc 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,7 +1,7 @@ /* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */ /* global Notes */ -import 'vendor/autosize'; +import 'autosize'; import '~/gl_form'; import '~/lib/utils/text_utility'; import '~/render_gfm'; @@ -39,7 +39,12 @@ import '~/notes'; loadFixtures(commentsTemplate); gl.utils.disableButtonIfEmptyField = _.noop; window.project_uploads_path = 'http://test.host/uploads'; - $('body').data('page', 'projects:merge_requets:show'); + $('body').attr('data-page', 'projects:merge_requets:show'); + }); + + afterEach(() => { + // Undo what we did to the shared <body> + $('body').removeAttr('data-page'); }); describe('task lists', function() { @@ -98,6 +103,16 @@ import '~/notes'; $('.js-comment-button').click(); expect(this.autoSizeSpy).toHaveBeenTriggered(); }); + + it('should not place escaped text in the comment box in case of error', function() { + const deferred = $.Deferred(); + spyOn($, 'ajax').and.returnValue(deferred.promise()); + $(textarea).text('A comment with `markup`.'); + + deferred.reject(); + $('.js-comment-button').click(); + expect($(textarea).val()).toEqual('A comment with `markup`.'); + }); }); describe('updateNote', () => { @@ -328,6 +343,7 @@ import '~/notes'; diff_discussion_html: false, }; $form = jasmine.createSpyObj('$form', ['closest', 'find']); + $form.length = 1; row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']); notes = jasmine.createSpyObj('notes', [ @@ -356,13 +372,29 @@ import '~/notes'; $form.closest.and.returnValues(row, $form); $form.find.and.returnValues(discussionContainer); body.attr.and.returnValue(''); - - Notes.prototype.renderDiscussionNote.call(notes, note, $form); }); it('should call Notes.animateAppendNote', () => { + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.discussion_html, $('.main-notes-list')); }); + + it('should append to row selected with line_code', () => { + $form.length = 0; + note.discussion_line_code = 'line_code'; + note.diff_discussion_html = '<tr></tr>'; + + const line = document.createElement('div'); + line.id = note.discussion_line_code; + document.body.appendChild(line); + + $form.closest.and.returnValues($form); + + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + + expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html); + }); }); describe('Discussion sub note', () => { @@ -426,19 +458,17 @@ import '~/notes'; }); describe('putEditFormInPlace', () => { - it('should call gl.GLForm with GFM parameter passed through', () => { - spyOn(gl, 'GLForm'); - - const $el = jasmine.createSpyObj('$form', ['find', 'closest']); - $el.find.and.returnValue($('<div>')); - $el.closest.and.returnValue($('<div>')); + it('should call GLForm with GFM parameter passed through', () => { + const notes = new Notes('', []); + const $el = $(` + <div> + <form></form> + </div> + `); - Notes.prototype.putEditFormInPlace.call({ - getEditFormSelector: () => '', - enableGFM: true - }, $el); + notes.putEditFormInPlace($el); - expect(gl.GLForm).toHaveBeenCalledWith(jasmine.any(Object), true); + expect(notes.glForm.enableGFM).toBeTruthy(); }); }); @@ -815,7 +845,7 @@ import '~/notes'; }); it('shows a flash message', () => { - this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline); + this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); expect($('.flash-alert').is(':visible')).toBeTruthy(); }); @@ -828,7 +858,7 @@ import '~/notes'; }); it('hides visible flash message', () => { - this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline); + this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0)); this.notes.clearFlash(); diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 85bd87318db..e8fcd4b1a36 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -11,7 +11,7 @@ describe('pipeline graph action component', () => { tooltipText: 'bar', link: 'foo', actionMethod: 'post', - actionIcon: 'icon_action_cancel', + actionIcon: 'cancel', }, }).$mount(); diff --git a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js index 25fd18b197e..ba721bc53c6 100644 --- a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js +++ b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js @@ -11,7 +11,7 @@ describe('action component', () => { tooltipText: 'bar', link: 'foo', actionMethod: 'post', - actionIcon: 'icon_action_cancel', + actionIcon: 'cancel', }, }).$mount(); diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js index e90593e0f40..342ee6c1242 100644 --- a/spec/javascripts/pipelines/graph/job_component_spec.js +++ b/spec/javascripts/pipelines/graph/job_component_spec.js @@ -14,7 +14,7 @@ describe('pipeline graph job component', () => { group: 'success', details_path: '/root/ci-mock/builds/4256', action: { - icon: 'icon_action_retry', + icon: 'retry', title: 'Retry', path: '/root/ci-mock/builds/4256/retry', method: 'post', diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js index 56c522b7f77..b9494f86d74 100644 --- a/spec/javascripts/pipelines/graph/mock_data.js +++ b/spec/javascripts/pipelines/graph/mock_data.js @@ -39,7 +39,7 @@ export default { "details_path": "/root/ci-mock/builds/4153", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4153/retry", "method": "post" @@ -62,7 +62,7 @@ export default { "details_path": "/root/ci-mock/builds/4153", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4153/retry", "method": "post" @@ -96,7 +96,7 @@ export default { "details_path": "/root/ci-mock/builds/4166", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4166/retry", "method": "post" @@ -119,7 +119,7 @@ export default { "details_path": "/root/ci-mock/builds/4166", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4166/retry", "method": "post" @@ -138,7 +138,7 @@ export default { "details_path": "/root/ci-mock/builds/4159", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4159/retry", "method": "post" @@ -161,7 +161,7 @@ export default { "details_path": "/root/ci-mock/builds/4159", "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", "action": { - "icon": "icon_action_retry", + "icon": "retry", "title": "Retry", "path": "/root/ci-mock/builds/4159/retry", "method": "post" diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index aa4d6eedaf4..063ab53681b 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -13,7 +13,7 @@ describe('stage column component', () => { group: 'success', details_path: '/root/ci-mock/builds/4256', action: { - icon: 'icon_action_retry', + icon: 'retry', title: 'Retry', path: '/root/ci-mock/builds/4256/retry', method: 'post', diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 256fdbe743c..4a4f2259d23 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -125,4 +125,23 @@ describe('Pipeline Url Component', () => { component.$el.querySelector('.js-pipeline-url-autodevops').textContent.trim(), ).toEqual('Auto DevOps'); }); + + it('should render error badge when pipeline has a failure reason set', () => { + const component = new PipelineUrlComponent({ + propsData: { + pipeline: { + id: 1, + path: 'foo', + flags: { + failure_reason: true, + }, + failure_reason: 'some reason', + }, + autoDevopsHelpPath: 'foo', + }, + }).$mount(); + + expect(component.$el.querySelector('.js-pipeline-url-failure').textContent).toContain('error'); + expect(component.$el.querySelector('.js-pipeline-url-failure').getAttribute('data-original-title')).toContain('some reason'); + }); }); diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index d7456a48bc1..a9126d2f4e9 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -10,6 +10,7 @@ describe('Pipelines Table Row', () => { propsData: { pipeline, autoDevopsHelpPath: 'foo', + viewType: 'root', }, }).$mount(); }; diff --git a/spec/javascripts/pipelines/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js index 4f5eb42eb35..ca2f9163313 100644 --- a/spec/javascripts/pipelines/pipelines_table_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_spec.js @@ -23,6 +23,7 @@ describe('Pipelines Table', () => { propsData: { pipelines: [], autoDevopsHelpPath: 'foo', + viewType: 'root', }, }).$mount(); }); @@ -49,6 +50,7 @@ describe('Pipelines Table', () => { propsData: { pipelines: [], autoDevopsHelpPath: 'foo', + viewType: 'root', }, }).$mount(); expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0); @@ -61,6 +63,7 @@ describe('Pipelines Table', () => { propsData: { pipelines: [pipeline], autoDevopsHelpPath: 'foo', + viewType: 'root', }, }).$mount(); diff --git a/spec/javascripts/profile/account/components/delete_account_modal_spec.js b/spec/javascripts/profile/account/components/delete_account_modal_spec.js new file mode 100644 index 00000000000..2e94948cfb2 --- /dev/null +++ b/spec/javascripts/profile/account/components/delete_account_modal_spec.js @@ -0,0 +1,129 @@ +import Vue from 'vue'; + +import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue'; + +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('DeleteAccountModal component', () => { + const actionUrl = `${gl.TEST_HOST}/delete/user`; + const username = 'hasnoname'; + let Component; + let vm; + + beforeEach(() => { + Component = Vue.extend(deleteAccountModal); + }); + + afterEach(() => { + vm.$destroy(); + }); + + const findElements = () => { + const confirmation = vm.confirmWithPassword ? 'password' : 'username'; + return { + form: vm.$refs.form, + input: vm.$el.querySelector(`[name="${confirmation}"]`), + submitButton: vm.$el.querySelector('.btn-danger'), + }; + }; + + describe('with password confirmation', () => { + beforeEach((done) => { + vm = mountComponent(Component, { + actionUrl, + confirmWithPassword: true, + username, + }); + + vm.isOpen = true; + + Vue.nextTick() + .then(done) + .catch(done.fail); + }); + + it('does not accept empty password', (done) => { + const { form, input, submitButton } = findElements(); + spyOn(form, 'submit'); + input.value = ''; + input.dispatchEvent(new Event('input')); + + Vue.nextTick() + .then(() => { + expect(vm.enteredPassword).toBe(input.value); + expect(submitButton).toHaveClass('disabled'); + submitButton.click(); + expect(form.submit).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('submits form with password', (done) => { + const { form, input, submitButton } = findElements(); + spyOn(form, 'submit'); + input.value = 'anything'; + input.dispatchEvent(new Event('input')); + + Vue.nextTick() + .then(() => { + expect(vm.enteredPassword).toBe(input.value); + expect(submitButton).not.toHaveClass('disabled'); + submitButton.click(); + expect(form.submit).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('with username confirmation', () => { + beforeEach((done) => { + vm = mountComponent(Component, { + actionUrl, + confirmWithPassword: false, + username, + }); + + vm.isOpen = true; + + Vue.nextTick() + .then(done) + .catch(done.fail); + }); + + it('does not accept wrong username', (done) => { + const { form, input, submitButton } = findElements(); + spyOn(form, 'submit'); + input.value = 'this is wrong'; + input.dispatchEvent(new Event('input')); + + Vue.nextTick() + .then(() => { + expect(vm.enteredUsername).toBe(input.value); + expect(submitButton).toHaveClass('disabled'); + submitButton.click(); + expect(form.submit).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('submits form with correct username', (done) => { + const { form, input, submitButton } = findElements(); + spyOn(form, 'submit'); + input.value = username; + input.dispatchEvent(new Event('input')); + + Vue.nextTick() + .then(() => { + expect(vm.enteredUsername).toBe(input.value); + expect(submitButton).not.toHaveClass('disabled'); + submitButton.click(); + expect(form.submit).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/projects_dropdown/service/projects_service_spec.js b/spec/javascripts/projects_dropdown/service/projects_service_spec.js index d5dd8b3449a..cfd1bb7d24f 100644 --- a/spec/javascripts/projects_dropdown/service/projects_service_spec.js +++ b/spec/javascripts/projects_dropdown/service/projects_service_spec.js @@ -34,7 +34,7 @@ describe('ProjectsService', () => { const searchQuery = 'lab'; const queryParams = { - simple: false, + simple: true, per_page: 20, membership: true, order_by: 'last_activity_at', diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js index 2b3a821dbd9..b24567ffc0c 100644 --- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js @@ -109,12 +109,16 @@ describe('PrometheusMetrics', () => { it('should show loader animation while response is being loaded and hide it when request is complete', (done) => { const deferred = $.Deferred(); - spyOn($, 'getJSON').and.returnValue(deferred.promise()); + spyOn($, 'ajax').and.returnValue(deferred.promise()); prometheusMetrics.loadActiveMetrics(); expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy(); - expect($.getJSON).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint); + expect($.ajax).toHaveBeenCalledWith({ + url: prometheusMetrics.activeMetricsEndpoint, + dataType: 'json', + global: false, + }); deferred.resolve({ data: metrics, success: true }); @@ -126,7 +130,7 @@ describe('PrometheusMetrics', () => { it('should show empty state if response failed to load', (done) => { const deferred = $.Deferred(); - spyOn($, 'getJSON').and.returnValue(deferred.promise()); + spyOn($, 'ajax').and.returnValue(deferred.promise()); spyOn(prometheusMetrics, 'populateActiveMetrics'); prometheusMetrics.loadActiveMetrics(); @@ -142,7 +146,7 @@ describe('PrometheusMetrics', () => { it('should populate metrics list once response is loaded', (done) => { const deferred = $.Deferred(); - spyOn($, 'getJSON').and.returnValue(deferred.promise()); + spyOn($, 'ajax').and.returnValue(deferred.promise()); spyOn(prometheusMetrics, 'populateActiveMetrics'); prometheusMetrics.loadActiveMetrics(); diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js new file mode 100644 index 00000000000..43e7d9e1224 --- /dev/null +++ b/spec/javascripts/registry/components/app_spec.js @@ -0,0 +1,122 @@ +import Vue from 'vue'; +import registry from '~/registry/components/app.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; +import { reposServerResponse } from '../mock_data'; + +describe('Registry List', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(registry); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with data', () => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(reposServerResponse), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + vm = mountComponent(Component, { endpoint: 'foo' }); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + it('should render a list of repos', (done) => { + setTimeout(() => { + expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length); + + Vue.nextTick(() => { + expect( + vm.$el.querySelectorAll('.container-image').length, + ).toEqual(reposServerResponse.length); + done(); + }); + }, 0); + }); + + describe('delete repository', () => { + it('should be possible to delete a repo', (done) => { + setTimeout(() => { + Vue.nextTick(() => { + expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined(); + done(); + }); + }, 0); + }); + }); + + describe('toggle repository', () => { + it('should open the container', (done) => { + setTimeout(() => { + Vue.nextTick(() => { + vm.$el.querySelector('.js-toggle-repo').click(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.js-toggle-repo i').className).toEqual('fa fa-chevron-up'); + done(); + }); + }); + }, 0); + }); + }); + }); + + describe('without data', () => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + vm = mountComponent(Component, { endpoint: 'foo' }); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + it('should render empty message', (done) => { + setTimeout(() => { + expect( + vm.$el.querySelector('p').textContent.trim(), + ).toEqual('No container images stored for this project. Add one by following the instructions above.'); + done(); + }, 0); + }); + }); + + describe('while loading data', () => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(reposServerResponse), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + vm = mountComponent(Component, { endpoint: 'foo' }); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + it('should render a loading spinner', (done) => { + Vue.nextTick(() => { + expect(vm.$el.querySelector('.fa-spinner')).not.toBe(null); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js new file mode 100644 index 00000000000..5891921318a --- /dev/null +++ b/spec/javascripts/registry/components/collapsible_container_spec.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import collapsibleComponent from '~/registry/components/collapsible_container.vue'; +import store from '~/registry/stores'; +import { repoPropsData } from '../mock_data'; + +describe('collapsible registry container', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(collapsibleComponent); + vm = new Component({ + store, + propsData: { + repo: repoPropsData, + }, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('toggle', () => { + it('should be closed by default', () => { + expect(vm.$el.querySelector('.container-image-tags')).toBe(null); + expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-right'); + }); + + it('should be open when user clicks on closed repo', (done) => { + vm.$el.querySelector('.js-toggle-repo').click(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.container-image-tags')).toBeDefined(); + expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-up'); + done(); + }); + }); + + it('should be closed when the user clicks on an opened repo', (done) => { + vm.$el.querySelector('.js-toggle-repo').click(); + + Vue.nextTick(() => { + vm.$el.querySelector('.js-toggle-repo').click(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.container-image-tags')).toBe(null); + expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-right'); + done(); + }); + }); + }); + }); + + describe('delete repo', () => { + it('should be possible to delete a repo', () => { + expect(vm.$el.querySelector('.js-remove-repo')).toBeDefined(); + }); + }); +}); diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js new file mode 100644 index 00000000000..6aa61afc445 --- /dev/null +++ b/spec/javascripts/registry/components/table_registry_spec.js @@ -0,0 +1,49 @@ +import Vue from 'vue'; +import tableRegistry from '~/registry/components/table_registry.vue'; +import store from '~/registry/stores'; +import { repoPropsData } from '../mock_data'; + +describe('table registry', () => { + let vm; + let Component; + + beforeEach(() => { + Component = Vue.extend(tableRegistry); + vm = new Component({ + store, + propsData: { + repo: repoPropsData, + }, + }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a table with the registry list', () => { + expect( + vm.$el.querySelectorAll('table tbody tr').length, + ).toEqual(repoPropsData.list.length); + }); + + it('should render registry tag', () => { + const textRendered = vm.$el.querySelector('.table tbody tr').textContent.trim().replace(/\s\s+/g, ' '); + expect(textRendered).toContain(repoPropsData.list[0].tag); + expect(textRendered).toContain(repoPropsData.list[0].shortRevision); + expect(textRendered).toContain(repoPropsData.list[0].layers); + expect(textRendered).toContain(repoPropsData.list[0].size); + }); + + it('should be possible to delete a registry', () => { + expect( + vm.$el.querySelector('.table tbody tr .js-delete-registry'), + ).toBeDefined(); + }); + + describe('pagination', () => { + it('should be possible to change the page', () => { + expect(vm.$el.querySelector('.gl-pagination')).toBeDefined(); + }); + }); +}); diff --git a/spec/javascripts/registry/getters_spec.js b/spec/javascripts/registry/getters_spec.js new file mode 100644 index 00000000000..3d989541881 --- /dev/null +++ b/spec/javascripts/registry/getters_spec.js @@ -0,0 +1,43 @@ +import * as getters from '~/registry/stores/getters'; + +describe('Getters Registry Store', () => { + let state; + + beforeEach(() => { + state = { + isLoading: false, + endpoint: '/root/empty-project/container_registry.json', + repos: [{ + canDelete: true, + destroyPath: 'bar', + id: '134', + isLoading: false, + list: [], + location: 'foo', + name: 'gitlab-org/omnibus-gitlab/foo', + tagsPath: 'foo', + }, { + canDelete: true, + destroyPath: 'bar', + id: '123', + isLoading: false, + list: [], + location: 'foo', + name: 'gitlab-org/omnibus-gitlab', + tagsPath: 'foo', + }], + }; + }); + + describe('isLoading', () => { + it('should return the isLoading property', () => { + expect(getters.isLoading(state)).toEqual(state.isLoading); + }); + }); + + describe('repos', () => { + it('should return the repos', () => { + expect(getters.repos(state)).toEqual(state.repos); + }); + }); +}); diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js new file mode 100644 index 00000000000..6bffb47be55 --- /dev/null +++ b/spec/javascripts/registry/mock_data.js @@ -0,0 +1,122 @@ +export const defaultState = { + isLoading: false, + endpoint: '', + repos: [], +}; + +export const reposServerResponse = [ + { + destroy_path: 'path', + id: '123', + location: 'location', + path: 'foo', + tags_path: 'tags_path', + }, + { + destroy_path: 'path_', + id: '456', + location: 'location_', + path: 'bar', + tags_path: 'tags_path_', + }, +]; + +export const registryServerResponse = [ + { + name: 'centos7', + short_revision: 'b118ab5b0', + revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', + total_size: 679, + layers: 19, + location: 'location', + created_at: 1505828744434, + destroy_path: 'path_', + }, + { + name: 'centos6', + short_revision: 'b118ab5b0', + revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', + total_size: 679, + layers: 19, + location: 'location', + created_at: 1505828744434, + }]; + +export const parsedReposServerResponse = [ + { + canDelete: true, + destroyPath: reposServerResponse[0].destroy_path, + id: reposServerResponse[0].id, + isLoading: false, + list: [], + location: reposServerResponse[0].location, + name: reposServerResponse[0].path, + tagsPath: reposServerResponse[0].tags_path, + }, + { + canDelete: true, + destroyPath: reposServerResponse[1].destroy_path, + id: reposServerResponse[1].id, + isLoading: false, + list: [], + location: reposServerResponse[1].location, + name: reposServerResponse[1].path, + tagsPath: reposServerResponse[1].tags_path, + }, +]; + +export const parsedRegistryServerResponse = [ + { + tag: registryServerResponse[0].name, + revision: registryServerResponse[0].revision, + shortRevision: registryServerResponse[0].short_revision, + size: registryServerResponse[0].total_size, + layers: registryServerResponse[0].layers, + location: registryServerResponse[0].location, + createdAt: registryServerResponse[0].created_at, + destroyPath: registryServerResponse[0].destroy_path, + canDelete: true, + }, + { + tag: registryServerResponse[1].name, + revision: registryServerResponse[1].revision, + shortRevision: registryServerResponse[1].short_revision, + size: registryServerResponse[1].total_size, + layers: registryServerResponse[1].layers, + location: registryServerResponse[1].location, + createdAt: registryServerResponse[1].created_at, + destroyPath: registryServerResponse[1].destroy_path, + canDelete: false, + }, +]; + +export const repoPropsData = { + canDelete: true, + destroyPath: 'path', + id: '123', + isLoading: false, + list: [ + { + tag: 'centos6', + revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', + shortRevision: 'b118ab5b0', + size: 19, + layers: 10, + location: 'location', + createdAt: 1505828744434, + destroyPath: 'path', + canDelete: true, + }, + ], + location: 'location', + name: 'foo', + tagsPath: 'path', + pagination: { + perPage: 5, + page: 1, + total: 13, + totalPages: 1, + nextPage: null, + previousPage: null, + }, +}; diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js new file mode 100644 index 00000000000..3c9da4f107b --- /dev/null +++ b/spec/javascripts/registry/stores/actions_spec.js @@ -0,0 +1,85 @@ +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import _ from 'underscore'; +import * as actions from '~/registry/stores/actions'; +import * as types from '~/registry/stores/mutation_types'; +import testAction from '../../helpers/vuex_action_helper'; +import { + defaultState, + reposServerResponse, + registryServerResponse, + parsedReposServerResponse, +} from '../mock_data'; + +Vue.use(VueResource); + +describe('Actions Registry Store', () => { + let interceptor; + let mockedState; + + beforeEach(() => { + mockedState = defaultState; + }); + + describe('server requests', () => { + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); + + describe('fetchRepos', () => { + beforeEach(() => { + interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(reposServerResponse), { + status: 200, + })); + }; + + Vue.http.interceptors.push(interceptor); + }); + + it('should set receveived repos', (done) => { + testAction(actions.fetchRepos, null, mockedState, [ + { type: types.TOGGLE_MAIN_LOADING }, + { type: types.SET_REPOS_LIST, payload: reposServerResponse }, + ], done); + }); + }); + + describe('fetchList', () => { + beforeEach(() => { + interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(registryServerResponse), { + status: 200, + })); + }; + + Vue.http.interceptors.push(interceptor); + }); + + it('should set received list', (done) => { + mockedState.repos = parsedReposServerResponse; + + testAction(actions.fetchList, { repo: mockedState.repos[1] }, mockedState, [ + { type: types.TOGGLE_REGISTRY_LIST_LOADING }, + { type: types.SET_REGISTRY_LIST, payload: registryServerResponse }, + ], done); + }); + }); + }); + + describe('setMainEndpoint', () => { + it('should commit set main endpoint', (done) => { + testAction(actions.setMainEndpoint, 'endpoint', mockedState, [ + { type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }, + ], done); + }); + }); + + describe('toggleLoading', () => { + it('should commit toggle main loading', (done) => { + testAction(actions.toggleLoading, null, mockedState, [ + { type: types.TOGGLE_MAIN_LOADING }, + ], done); + }); + }); +}); diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/javascripts/registry/stores/mutations_spec.js new file mode 100644 index 00000000000..2e4c0659daa --- /dev/null +++ b/spec/javascripts/registry/stores/mutations_spec.js @@ -0,0 +1,81 @@ +import mutations from '~/registry/stores/mutations'; +import * as types from '~/registry/stores/mutation_types'; +import { + defaultState, + reposServerResponse, + registryServerResponse, + parsedReposServerResponse, + parsedRegistryServerResponse, +} from '../mock_data'; + +describe('Mutations Registry Store', () => { + let mockState; + beforeEach(() => { + mockState = defaultState; + }); + + describe('SET_MAIN_ENDPOINT', () => { + it('should set the main endpoint', () => { + const expectedState = Object.assign({}, mockState, { endpoint: 'foo' }); + mutations[types.SET_MAIN_ENDPOINT](mockState, 'foo'); + expect(mockState).toEqual(expectedState); + }); + }); + + describe('SET_REPOS_LIST', () => { + it('should set a parsed repository list', () => { + mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); + expect(mockState.repos).toEqual(parsedReposServerResponse); + }); + }); + + describe('TOGGLE_MAIN_LOADING', () => { + it('should set a parsed repository list', () => { + mutations[types.TOGGLE_MAIN_LOADING](mockState); + expect(mockState.isLoading).toEqual(true); + }); + }); + + describe('SET_REGISTRY_LIST', () => { + it('should set a list of registries in a specific repository', () => { + mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); + mutations[types.SET_REGISTRY_LIST](mockState, { + repo: mockState.repos[0], + resp: registryServerResponse, + headers: { + 'x-per-page': 2, + 'x-page': 1, + 'x-total': 10, + }, + }); + + expect(mockState.repos[0].list).toEqual(parsedRegistryServerResponse); + expect(mockState.repos[0].pagination).toEqual({ + perPage: 2, + page: 1, + total: 10, + totalPages: NaN, + nextPage: NaN, + previousPage: NaN, + }); + }); + }); + + describe('TOGGLE_REGISTRY_LIST_LOADING', () => { + it('should toggle isLoading property for a specific repository', () => { + mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); + mutations[types.SET_REGISTRY_LIST](mockState, { + repo: mockState.repos[0], + resp: registryServerResponse, + headers: { + 'x-per-page': 2, + 'x-page': 1, + 'x-total': 10, + }, + }); + + mutations[types.TOGGLE_REGISTRY_LIST_LOADING](mockState, mockState.repos[0]); + expect(mockState.repos[0].isLoading).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js new file mode 100644 index 00000000000..9a705a1f0ed --- /dev/null +++ b/spec/javascripts/repo/components/new_branch_form_spec.js @@ -0,0 +1,114 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import newBranchForm from '~/repo/components/new_branch_form.vue'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { resetStore } from '../helpers'; + +describe('Multi-file editor new branch form', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(newBranchForm); + + vm = createComponentWithStore(Component, store); + + vm.$store.state.currentBranch = 'master'; + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + describe('template', () => { + it('renders submit as disabled', () => { + expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBe('disabled'); + }); + + it('enables the submit button when branch is not empty', (done) => { + vm.branchName = 'testing'; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBeNull(); + + done(); + }); + }); + + it('displays current branch creating from', (done) => { + Vue.nextTick(() => { + expect(vm.$el.querySelector('p').textContent.replace(/\s+/g, ' ').trim()).toBe('Create from: master'); + + done(); + }); + }); + }); + + describe('submitNewBranch', () => { + beforeEach(() => { + spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve()); + }); + + it('sets to loading', () => { + vm.submitNewBranch(); + + expect(vm.loading).toBeTruthy(); + }); + + it('hides current flash element', (done) => { + vm.$refs.flashContainer.innerHTML = '<div class="flash-alert"></div>'; + + vm.submitNewBranch(); + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.flash-alert')).toBeNull(); + + done(); + }); + }); + + it('calls createdNewBranch with branchName', () => { + vm.branchName = 'testing'; + + vm.submitNewBranch(); + + expect(vm.createNewBranch).toHaveBeenCalledWith('testing'); + }); + }); + + describe('submitNewBranch with error', () => { + beforeEach(() => { + spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({ + json: () => Promise.resolve({ + message: 'error message', + }), + })); + }); + + it('sets loading to false', (done) => { + vm.loading = true; + + vm.submitNewBranch(); + + setTimeout(() => { + expect(vm.loading).toBeFalsy(); + + done(); + }); + }); + + it('creates flash element', (done) => { + vm.submitNewBranch(); + + setTimeout(() => { + expect(vm.$el.querySelector('.flash-alert')).not.toBeNull(); + expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message'); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js new file mode 100644 index 00000000000..93b10fc1fee --- /dev/null +++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js @@ -0,0 +1,71 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import newDropdown from '~/repo/components/new_dropdown/index.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { resetStore } from '../../helpers'; + +describe('new dropdown component', () => { + let vm; + + beforeEach(() => { + const component = Vue.extend(newDropdown); + + vm = createComponentWithStore(component, store); + + vm.$store.state.path = ''; + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('renders new file and new directory links', () => { + expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); + expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('New directory'); + }); + + describe('createNewItem', () => { + it('sets modalType to blob when new file is clicked', () => { + vm.$el.querySelectorAll('a')[0].click(); + + expect(vm.modalType).toBe('blob'); + }); + + it('sets modalType to tree when new directory is clicked', () => { + vm.$el.querySelectorAll('a')[1].click(); + + expect(vm.modalType).toBe('tree'); + }); + + it('opens modal when link is clicked', (done) => { + vm.$el.querySelectorAll('a')[0].click(); + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.modal')).not.toBeNull(); + + done(); + }); + }); + }); + + describe('toggleModalOpen', () => { + it('closes modal after toggling', (done) => { + vm.toggleModalOpen(); + + Vue.nextTick() + .then(() => { + expect(vm.$el.querySelector('.modal')).not.toBeNull(); + }) + .then(vm.toggleModalOpen) + .then(() => { + expect(vm.$el.querySelector('.modal')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js new file mode 100644 index 00000000000..1ff7590ec79 --- /dev/null +++ b/spec/javascripts/repo/components/new_dropdown/modal_spec.js @@ -0,0 +1,198 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import modal from '~/repo/components/new_dropdown/modal.vue'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { file, resetStore } from '../../helpers'; + +describe('new file modal component', () => { + const Component = Vue.extend(modal); + let vm; + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + ['tree', 'blob'].forEach((type) => { + describe(type, () => { + beforeEach(() => { + vm = createComponentWithStore(Component, store, { + type, + path: '', + }).$mount(); + + vm.entryName = 'testing'; + }); + + it(`sets modal title as ${type}`, () => { + const title = type === 'tree' ? 'directory' : 'file'; + + expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`); + }); + + it(`sets button label as ${type}`, () => { + const title = type === 'tree' ? 'directory' : 'file'; + + expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`); + }); + + it(`sets form label as ${type}`, () => { + const title = type === 'tree' ? 'Directory' : 'File'; + + expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`); + }); + + describe('createEntryInStore', () => { + it('calls createTempEntry', () => { + spyOn(vm, 'createTempEntry'); + + vm.createEntryInStore(); + + expect(vm.createTempEntry).toHaveBeenCalledWith({ + name: 'testing', + type, + }); + }); + + it('sets editMode to true', (done) => { + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.editMode).toBeTruthy(); + + done(); + }); + }); + + it('toggles blob view', (done) => { + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.currentBlobView).toBe('repo-editor'); + + done(); + }); + }); + + it('opens newly created file', (done) => { + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.openFiles.length).toBe(1); + expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep'); + + done(); + }); + }); + + it(`creates ${type} in the current stores path`, (done) => { + vm.$store.state.path = 'app'; + + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.tree[0].path).toBe('app/testing'); + expect(vm.$store.state.tree[0].name).toBe('testing'); + + if (type === 'tree') { + expect(vm.$store.state.tree[0].tree.length).toBe(1); + } + + done(); + }); + }); + + if (type === 'blob') { + it('creates new file', (done) => { + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('testing'); + expect(vm.$store.state.tree[0].type).toBe('blob'); + expect(vm.$store.state.tree[0].tempFile).toBeTruthy(); + + done(); + }); + }); + + it('does not create temp file when file already exists', (done) => { + vm.$store.state.tree.push(file('testing', '1', type)); + + vm.createEntryInStore(); + + setTimeout(() => { + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('testing'); + expect(vm.$store.state.tree[0].type).toBe('blob'); + expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); + + done(); + }); + }); + } else { + it('creates new tree', () => { + vm.createEntryInStore(); + + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('testing'); + expect(vm.$store.state.tree[0].type).toBe('tree'); + expect(vm.$store.state.tree[0].tempFile).toBeTruthy(); + expect(vm.$store.state.tree[0].tree.length).toBe(1); + expect(vm.$store.state.tree[0].tree[0].name).toBe('.gitkeep'); + }); + + it('creates multiple trees when entryName has slashes', () => { + vm.entryName = 'app/test'; + vm.createEntryInStore(); + + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('app'); + expect(vm.$store.state.tree[0].tree[0].name).toBe('test'); + expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep'); + }); + + it('creates tree in existing tree', () => { + vm.$store.state.tree.push(file('app', '1', 'tree')); + + vm.entryName = 'app/test'; + vm.createEntryInStore(); + + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('app'); + expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); + expect(vm.$store.state.tree[0].tree[0].tempFile).toBeTruthy(); + expect(vm.$store.state.tree[0].tree[0].name).toBe('test'); + expect(vm.$store.state.tree[0].tree[0].tree[0].name).toBe('.gitkeep'); + }); + + it('does not create new tree when already exists', () => { + vm.$store.state.tree.push(file('app', '1', 'tree')); + + vm.entryName = 'app'; + vm.createEntryInStore(); + + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe('app'); + expect(vm.$store.state.tree[0].tempFile).toBeFalsy(); + expect(vm.$store.state.tree[0].tree.length).toBe(0); + }); + } + }); + }); + }); + + it('focuses field on mount', () => { + document.body.innerHTML += '<div class="js-test"></div>'; + + vm = createComponentWithStore(Component, store, { + type: 'tree', + path: '', + }).$mount('.js-test'); + + expect(document.activeElement).toBe(vm.$refs.fieldName); + + vm.$el.remove(); + }); +}); diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js new file mode 100644 index 00000000000..bf7893029b1 --- /dev/null +++ b/spec/javascripts/repo/components/new_dropdown/upload_spec.js @@ -0,0 +1,103 @@ +import Vue from 'vue'; +import upload from '~/repo/components/new_dropdown/upload.vue'; +import store from '~/repo/stores'; +import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { resetStore } from '../../helpers'; + +describe('new dropdown upload', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(upload); + + vm = createComponentWithStore(Component, store, { + path: '', + }); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + describe('readFile', () => { + beforeEach(() => { + spyOn(FileReader.prototype, 'readAsText'); + spyOn(FileReader.prototype, 'readAsDataURL'); + }); + + it('calls readAsText for text files', () => { + const file = { + type: 'text/html', + }; + + vm.readFile(file); + + expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file); + }); + + it('calls readAsDataURL for non-text files', () => { + const file = { + type: 'images/png', + }; + + vm.readFile(file); + + expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file); + }); + }); + + describe('createFile', () => { + const target = { + result: 'content', + }; + const binaryTarget = { + result: 'base64,base64content', + }; + const file = { + name: 'file', + }; + + it('creates new file', (done) => { + vm.createFile(target, file, true); + + vm.$nextTick(() => { + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe(file.name); + expect(vm.$store.state.tree[0].content).toBe(target.result); + + done(); + }); + }); + + it('creates new file in path', (done) => { + vm.$store.state.path = 'testing'; + vm.createFile(target, file, true); + + vm.$nextTick(() => { + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe(file.name); + expect(vm.$store.state.tree[0].content).toBe(target.result); + expect(vm.$store.state.tree[0].path).toBe(`testing/${file.name}`); + + done(); + }); + }); + + it('splits content on base64 if binary', (done) => { + vm.createFile(binaryTarget, file, false); + + vm.$nextTick(() => { + expect(vm.$store.state.tree.length).toBe(1); + expect(vm.$store.state.tree[0].name).toBe(file.name); + expect(vm.$store.state.tree[0].content).toBe(binaryTarget.result.split('base64,')[1]); + expect(vm.$store.state.tree[0].base64).toBe(true); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js index e604dcc152d..0f991e1b727 100644 --- a/spec/javascripts/repo/components/repo_commit_section_spec.js +++ b/spec/javascripts/repo/components/repo_commit_section_spec.js @@ -1,49 +1,43 @@ import Vue from 'vue'; +import store from '~/repo/stores'; +import service from '~/repo/services'; import repoCommitSection from '~/repo/components/repo_commit_section.vue'; -import RepoStore from '~/repo/stores/repo_store'; -import RepoService from '~/repo/services/repo_service'; +import getSetTimeoutPromise from '../../helpers/set_timeout_promise_helper'; +import { file, resetStore } from '../helpers'; describe('RepoCommitSection', () => { - const branch = 'master'; - const projectUrl = 'projectUrl'; - const changedFiles = [{ - id: 0, - changed: true, - url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`, - path: 'dir/file0.ext', - newContent: 'a', - }, { - id: 1, - changed: true, - url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`, - path: 'dir/file1.ext', - newContent: 'b', - }]; - const openedFiles = changedFiles.concat([{ - id: 2, - url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`, - path: 'dir/file2.ext', - changed: false, - }]); - - RepoStore.projectUrl = projectUrl; - - function createComponent(el) { + let vm; + + function createComponent() { const RepoCommitSection = Vue.extend(repoCommitSection); - return new RepoCommitSection().$mount(el); + const comp = new RepoCommitSection({ + store, + }).$mount(); + + comp.$store.state.currentBranch = 'master'; + comp.$store.state.openFiles = [file(), file()]; + comp.$store.state.openFiles.forEach(f => Object.assign(f, { + changed: true, + content: 'testing', + })); + + return comp.$mount(); } - it('renders a commit section', () => { - RepoStore.isCommitable = true; - RepoStore.currentBranch = branch; - RepoStore.targetBranch = branch; - RepoStore.openedFiles = openedFiles; + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); - const vm = createComponent(); + it('renders a commit section', () => { const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')]; - const commitMessage = vm.$el.querySelector('#commit-message'); - const submitCommit = vm.$refs.submitCommit; + const submitCommit = vm.$el.querySelector('.btn'); const targetBranch = vm.$el.querySelector('.target-branch'); expect(vm.$el.querySelector(':scope > form')).toBeTruthy(); @@ -51,109 +45,70 @@ describe('RepoCommitSection', () => { expect(changedFileElements.length).toEqual(2); changedFileElements.forEach((changedFile, i) => { - expect(changedFile.textContent.trim()).toEqual(changedFiles[i].path); + expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path); }); - expect(commitMessage.tagName).toEqual('TEXTAREA'); - expect(commitMessage.name).toEqual('commit-message'); - expect(submitCommit.type).toEqual('submit'); expect(submitCommit.disabled).toBeTruthy(); expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy(); expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files'); expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch'); - expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual(branch); + expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual('master'); }); - it('does not render if not isCommitable', () => { - RepoStore.isCommitable = false; - RepoStore.openedFiles = [{ - id: 0, - changed: true, - }]; + describe('when submitting', () => { + let changedFiles; - const vm = createComponent(); + beforeEach(() => { + vm.commitMessage = 'testing'; + changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles)); - expect(vm.$el.innerHTML).toBeFalsy(); - }); + spyOn(service, 'commit').and.returnValue(Promise.resolve({ + short_id: '1', + stats: {}, + })); + }); - it('does not render if no changedFiles', () => { - RepoStore.isCommitable = true; - RepoStore.openedFiles = []; + it('allows you to submit', () => { + expect(vm.$el.querySelector('.btn').disabled).toBeTruthy(); + }); - const vm = createComponent(); + it('submits commit', (done) => { + vm.makeCommit(); + + // Wait for the branch check to finish + getSetTimeoutPromise() + .then(() => Vue.nextTick()) + .then(() => { + const args = service.commit.calls.allArgs()[0]; + const { commit_message, actions, branch: payloadBranch } = args[1]; + + expect(commit_message).toBe('testing'); + expect(actions.length).toEqual(2); + expect(payloadBranch).toEqual('master'); + expect(actions[0].action).toEqual('update'); + expect(actions[1].action).toEqual('update'); + expect(actions[0].content).toEqual(changedFiles[0].content); + expect(actions[1].content).toEqual(changedFiles[1].content); + expect(actions[0].file_path).toEqual(changedFiles[0].path); + expect(actions[1].file_path).toEqual(changedFiles[1].path); + }) + .then(done) + .catch(done.fail); + }); - expect(vm.$el.innerHTML).toBeFalsy(); - }); + it('redirects to MR creation page if start new MR checkbox checked', (done) => { + spyOn(gl.utils, 'visitUrl'); + vm.startNewMR = true; - it('shows commit submit and summary if commitMessage and spinner if submitCommitsLoading', (done) => { - const projectId = 'projectId'; - const commitMessage = 'commitMessage'; - RepoStore.isCommitable = true; - RepoStore.currentBranch = branch; - RepoStore.targetBranch = branch; - RepoStore.openedFiles = openedFiles; - RepoStore.projectId = projectId; - - // We need to append to body to get form `submit` events working - // Otherwise we run into, "Form submission canceled because the form is not connected" - // See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm - const el = document.createElement('div'); - document.body.appendChild(el); - - const vm = createComponent(el); - const commitMessageEl = vm.$el.querySelector('#commit-message'); - const submitCommit = vm.$refs.submitCommit; - - vm.commitMessage = commitMessage; - - Vue.nextTick(() => { - expect(commitMessageEl.value).toBe(commitMessage); - expect(submitCommit.disabled).toBeFalsy(); - - spyOn(vm, 'makeCommit').and.callThrough(); - spyOn(RepoService, 'commitFiles').and.callFake(() => Promise.resolve()); - - submitCommit.click(); - - Vue.nextTick(() => { - expect(vm.makeCommit).toHaveBeenCalled(); - expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeTruthy(); - - const args = RepoService.commitFiles.calls.allArgs()[0]; - const { commit_message, actions, branch: payloadBranch } = args[0]; - - expect(commit_message).toBe(commitMessage); - expect(actions.length).toEqual(2); - expect(payloadBranch).toEqual(branch); - expect(actions[0].action).toEqual('update'); - expect(actions[1].action).toEqual('update'); - expect(actions[0].content).toEqual(openedFiles[0].newContent); - expect(actions[1].content).toEqual(openedFiles[1].newContent); - expect(actions[0].file_path).toEqual(openedFiles[0].path); - expect(actions[1].file_path).toEqual(openedFiles[1].path); - - done(); - }); - }); - }); + vm.makeCommit(); - describe('methods', () => { - describe('resetCommitState', () => { - it('should reset store vars and scroll to top', () => { - const vm = { - submitCommitsLoading: true, - changedFiles: new Array(10), - commitMessage: 'commitMessage', - editMode: true, - }; - - repoCommitSection.methods.resetCommitState.call(vm); - - expect(vm.submitCommitsLoading).toEqual(false); - expect(vm.changedFiles).toEqual([]); - expect(vm.commitMessage).toEqual(''); - expect(vm.editMode).toEqual(false); - }); + getSetTimeoutPromise() + .then(() => Vue.nextTick()) + .then(() => { + expect(gl.utils.visitUrl).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js index 29dc2d21e4b..44018464b35 100644 --- a/spec/javascripts/repo/components/repo_edit_button_spec.js +++ b/spec/javascripts/repo/components/repo_edit_button_spec.js @@ -1,51 +1,83 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoEditButton from '~/repo/components/repo_edit_button.vue'; -import RepoStore from '~/repo/stores/repo_store'; +import { file, resetStore } from '../helpers'; describe('RepoEditButton', () => { - function createComponent() { + let vm; + + beforeEach(() => { + const f = file(); const RepoEditButton = Vue.extend(repoEditButton); - return new RepoEditButton().$mount(); - } + vm = new RepoEditButton({ + store, + }); - it('renders an edit button that toggles the view state', (done) => { - RepoStore.isCommitable = true; - RepoStore.changedFiles = []; - RepoStore.binary = false; - RepoStore.openedFiles = [{}, {}]; + f.active = true; + vm.$store.dispatch('setInitialData', { + canCommit: true, + onTopOfBranch: true, + }); + vm.$store.state.openFiles.push(f); + }); - const vm = createComponent(); + afterEach(() => { + vm.$destroy(); - expect(vm.$el.tagName).toEqual('BUTTON'); - expect(vm.$el.textContent).toMatch('Edit'); + resetStore(vm.$store); + }); - spyOn(vm, 'editCancelClicked').and.callThrough(); - spyOn(vm, 'toggleProjectRefsForm'); + it('renders an edit button', () => { + vm.$mount(); - vm.$el.click(); + expect(vm.$el.querySelector('.btn')).not.toBeNull(); + expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit'); + }); + + it('renders edit button with cancel text', () => { + vm.$store.state.editMode = true; + + vm.$mount(); + + expect(vm.$el.querySelector('.btn')).not.toBeNull(); + expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); + }); + + it('toggles edit mode on click', (done) => { + vm.$mount(); + + vm.$el.querySelector('.btn').click(); + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); - Vue.nextTick(() => { - expect(vm.editCancelClicked).toHaveBeenCalled(); - expect(vm.toggleProjectRefsForm).toHaveBeenCalled(); - expect(vm.$el.textContent).toMatch('Cancel edit'); done(); }); }); - it('does not render if not isCommitable', () => { - RepoStore.isCommitable = false; + describe('discardPopupOpen', () => { + beforeEach(() => { + vm.$store.state.discardPopupOpen = true; + vm.$store.state.editMode = true; + vm.$store.state.openFiles[0].changed = true; - const vm = createComponent(); + vm.$mount(); + }); - expect(vm.$el.innerHTML).toBeUndefined(); - }); + it('renders popup', () => { + expect(vm.$el.querySelector('.modal')).not.toBeNull(); + }); + + it('removes all changed files', (done) => { + vm.$el.querySelector('.btn-warning').click(); - describe('methods', () => { - describe('editCancelClicked', () => { - it('sets dialog to open when there are changedFiles'); + vm.$nextTick(() => { + expect(vm.$store.getters.changedFiles.length).toBe(0); + expect(vm.$el.querySelector('.modal')).toBeNull(); - it('toggles editMode and calls toggleBlobView'); + done(); + }); }); }); }); diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js index 85d55d171f9..979d2185076 100644 --- a/spec/javascripts/repo/components/repo_editor_spec.js +++ b/spec/javascripts/repo/components/repo_editor_spec.js @@ -1,49 +1,56 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoEditor from '~/repo/components/repo_editor.vue'; +import { file, resetStore } from '../helpers'; describe('RepoEditor', () => { + let vm; + beforeEach(() => { + const f = file(); const RepoEditor = Vue.extend(repoEditor); - this.vm = new RepoEditor().$mount(); + vm = new RepoEditor({ + store, + }); + + f.active = true; + f.tempFile = true; + vm.$store.state.openFiles.push(f); + vm.$store.getters.activeFile.html = 'testing'; + vm.monaco = true; + + vm.$mount(); }); - it('renders an ide container', (done) => { - this.vm.openedFiles = ['idiidid']; - this.vm.binary = false; + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + it('renders an ide container', (done) => { Vue.nextTick(() => { - expect(this.vm.shouldHideEditor).toBe(false); - expect(this.vm.$el.id).toEqual('ide'); - expect(this.vm.$el.tagName).toBe('DIV'); + expect(vm.shouldHideEditor).toBeFalsy(); + expect(vm.$el.textContent.trim()).toBe(''); + done(); }); }); - describe('when there are no open files', () => { - it('does not render the ide', (done) => { - this.vm.openedFiles = []; + describe('when open file is binary and not raw', () => { + beforeEach((done) => { + vm.$store.getters.activeFile.binary = true; - Vue.nextTick(() => { - expect(this.vm.shouldHideEditor).toBe(true); - expect(this.vm.$el.tagName).not.toBeDefined(); - done(); - }); + Vue.nextTick(done); }); - }); - describe('when open file is binary and not raw', () => { - it('does not render the IDE', (done) => { - this.vm.binary = true; - this.vm.activeFile = { - raw: false, - }; - - Vue.nextTick(() => { - expect(this.vm.shouldHideEditor).toBe(true); - expect(this.vm.$el.tagName).not.toBeDefined(); - done(); - }); + it('does not render the IDE', () => { + expect(vm.shouldHideEditor).toBeTruthy(); + }); + + it('shows activeFile html', () => { + expect(vm.$el.textContent.trim()).toBe('testing'); }); }); }); diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js index dfab51710c3..d6e255e4810 100644 --- a/spec/javascripts/repo/components/repo_file_buttons_spec.js +++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js @@ -1,75 +1,49 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoFileButtons from '~/repo/components/repo_file_buttons.vue'; -import RepoStore from '~/repo/stores/repo_store'; +import { file, resetStore } from '../helpers'; describe('RepoFileButtons', () => { + const activeFile = file(); + let vm; + function createComponent() { const RepoFileButtons = Vue.extend(repoFileButtons); - return new RepoFileButtons().$mount(); - } + activeFile.rawPath = 'test'; + activeFile.blamePath = 'test'; + activeFile.commitsPath = 'test'; + activeFile.active = true; + store.state.openFiles.push(activeFile); - it('renders Raw, Blame, History, Permalink and Preview toggle', () => { - const activeFile = { - extension: 'md', - url: 'url', - raw_path: 'raw_path', - blame_path: 'blame_path', - commits_path: 'commits_path', - permalink: 'permalink', - }; - const activeFileLabel = 'activeFileLabel'; - RepoStore.openedFiles = new Array(1); - RepoStore.activeFile = activeFile; - RepoStore.activeFileLabel = activeFileLabel; - RepoStore.editMode = true; - RepoStore.binary = false; + return new RepoFileButtons({ + store, + }).$mount(); + } - const vm = createComponent(); - const raw = vm.$el.querySelector('.raw'); - const blame = vm.$el.querySelector('.blame'); - const history = vm.$el.querySelector('.history'); + afterEach(() => { + vm.$destroy(); - expect(vm.$el.id).toEqual('repo-file-buttons'); - expect(raw.href).toMatch(`/${activeFile.raw_path}`); - expect(raw.textContent.trim()).toEqual('Raw'); - expect(blame.href).toMatch(`/${activeFile.blame_path}`); - expect(blame.textContent.trim()).toEqual('Blame'); - expect(history.href).toMatch(`/${activeFile.commits_path}`); - expect(history.textContent.trim()).toEqual('History'); - expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink'); - expect(vm.$el.querySelector('.preview').textContent.trim()).toEqual(activeFileLabel); + resetStore(vm.$store); }); - it('triggers rawPreviewToggle on preview click', () => { - const activeFile = { - extension: 'md', - url: 'url', - }; - RepoStore.openedFiles = new Array(1); - RepoStore.activeFile = activeFile; - RepoStore.editMode = true; - - const vm = createComponent(); - const preview = vm.$el.querySelector('.preview'); - - spyOn(vm, 'rawPreviewToggle'); - - preview.click(); - - expect(vm.rawPreviewToggle).toHaveBeenCalled(); - }); + it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => { + vm = createComponent(); - it('does not render preview toggle if not canPreview', () => { - const activeFile = { - extension: 'abcd', - url: 'url', - }; - RepoStore.openedFiles = new Array(1); - RepoStore.activeFile = activeFile; + vm.$nextTick(() => { + const raw = vm.$el.querySelector('.raw'); + const blame = vm.$el.querySelector('.blame'); + const history = vm.$el.querySelector('.history'); - const vm = createComponent(); + expect(raw.href).toMatch(`/${activeFile.rawPath}`); + expect(raw.textContent.trim()).toEqual('Raw'); + expect(blame.href).toMatch(`/${activeFile.blamePath}`); + expect(blame.textContent.trim()).toEqual('Blame'); + expect(history.href).toMatch(`/${activeFile.commitsPath}`); + expect(history.textContent.trim()).toEqual('History'); + expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink'); - expect(vm.$el.querySelector('.preview')).toBeFalsy(); + done(); + }); }); }); diff --git a/spec/javascripts/repo/components/repo_file_options_spec.js b/spec/javascripts/repo/components/repo_file_options_spec.js deleted file mode 100644 index 9759b4bf12d..00000000000 --- a/spec/javascripts/repo/components/repo_file_options_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import Vue from 'vue'; -import repoFileOptions from '~/repo/components/repo_file_options.vue'; - -describe('RepoFileOptions', () => { - const projectName = 'projectName'; - - function createComponent(propsData) { - const RepoFileOptions = Vue.extend(repoFileOptions); - - return new RepoFileOptions({ - propsData, - }).$mount(); - } - - it('renders the title and new file/folder buttons if isMini is true', () => { - const vm = createComponent({ - isMini: true, - projectName, - }); - - expect(vm.$el.classList.contains('repo-file-options')).toBeTruthy(); - expect(vm.$el.querySelector('.title').textContent).toEqual(projectName); - }); - - it('does not render if isMini is false', () => { - const vm = createComponent({ - isMini: false, - projectName, - }); - - expect(vm.$el.innerHTML).toBeFalsy(); - }); -}); diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index 518a2d25ecf..c45f8a18d1f 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -1,136 +1,115 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoFile from '~/repo/components/repo_file.vue'; +import { file, resetStore } from '../helpers'; describe('RepoFile', () => { const updated = 'updated'; - const file = { - icon: 'icon', - url: 'url', - name: 'name', - lastCommitMessage: 'message', - lastCommitUpdate: Date.now(), - level: 10, - }; - const activeFile = { - url: 'url', - }; + let vm; function createComponent(propsData) { const RepoFile = Vue.extend(repoFile); return new RepoFile({ + store, propsData, }).$mount(); } - beforeEach(() => { - spyOn(repoFile.mixins[0].methods, 'timeFormated').and.returnValue(updated); + afterEach(() => { + resetStore(vm.$store); }); it('renders link, icon, name and last commit details', () => { - const vm = createComponent({ - file, - activeFile, + const RepoFile = Vue.extend(repoFile); + vm = new RepoFile({ + store, + propsData: { + file: file(), + }, }); + spyOn(vm, 'timeFormated').and.returnValue(updated); + vm.$mount(); + const name = vm.$el.querySelector('.repo-file-name'); const fileIcon = vm.$el.querySelector('.file-icon'); - expect(vm.$el.classList.contains('active')).toBeTruthy(); - expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px'); - expect(name.title).toEqual(file.url); - expect(name.href).toMatch(`/${file.url}`); - expect(name.textContent.trim()).toEqual(file.name); - expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(file.lastCommitMessage); + expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); + expect(name.href).toMatch(`/${vm.file.url}`); + expect(name.textContent.trim()).toEqual(vm.file.name); + expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(vm.file.lastCommit.message); expect(vm.$el.querySelector('.commit-update').textContent.trim()).toBe(updated); - expect(fileIcon.classList.contains(file.icon)).toBeTruthy(); - expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`); + expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); + expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); }); it('does render if hasFiles is true and is loading tree', () => { - const vm = createComponent({ - file, - activeFile, - loading: { - tree: true, - }, - hasFiles: true, + vm = createComponent({ + file: file(), }); - expect(vm.$el.innerHTML).toBeTruthy(); expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); }); it('renders a spinner if the file is loading', () => { - file.loading = true; - const vm = createComponent({ - file, - activeFile, - loading: { - tree: true, - }, - hasFiles: true, + const f = file(); + f.loading = true; + vm = createComponent({ + file: f, }); - expect(vm.$el.innerHTML).toBeTruthy(); - expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${file.level * 10}px`); + expect(vm.$el.querySelector('.fa-spin.fa-spinner')).not.toBeNull(); + expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`); }); - it('does not render if loading tree', () => { - const vm = createComponent({ - file, - activeFile, - loading: { - tree: true, - }, + it('does not render commit message and datetime if mini', (done) => { + vm = createComponent({ + file: file(), }); + vm.$store.state.openFiles.push(vm.file); - expect(vm.$el.innerHTML).toBeFalsy(); - }); + vm.$nextTick(() => { + expect(vm.$el.querySelector('.commit-message')).toBeFalsy(); + expect(vm.$el.querySelector('.commit-update')).toBeFalsy(); - it('does not render commit message and datetime if mini', () => { - const vm = createComponent({ - file, - activeFile, - isMini: true, + done(); }); - - expect(vm.$el.querySelector('.commit-message')).toBeFalsy(); - expect(vm.$el.querySelector('.commit-update')).toBeFalsy(); }); - it('does not set active class if file is active file', () => { - const vm = createComponent({ - file, - activeFile: {}, + it('fires clickedTreeRow when the link is clicked', () => { + vm = createComponent({ + file: file(), }); - expect(vm.$el.classList.contains('active')).toBeFalsy(); - }); + spyOn(vm, 'clickedTreeRow'); - it('fires linkClicked when the link is clicked', () => { - const vm = createComponent({ - file, - activeFile, - }); + vm.$el.click(); - spyOn(vm, 'linkClicked'); + expect(vm.clickedTreeRow).toHaveBeenCalledWith(vm.file); + }); - vm.$el.querySelector('.repo-file-name').click(); + describe('submodule', () => { + let f; - expect(vm.linkClicked).toHaveBeenCalledWith(file); - }); + beforeEach(() => { + f = file('submodule name', '123456789'); + f.type = 'submodule'; - describe('methods', () => { - describe('linkClicked', () => { - const vm = jasmine.createSpyObj('vm', ['$emit']); + vm = createComponent({ + file: f, + }); + }); - it('$emits linkclicked with file obj', () => { - const theFile = {}; + afterEach(() => { + vm.$destroy(); + }); - repoFile.methods.linkClicked.call(vm, theFile); + it('renders submodule short ID', () => { + expect(vm.$el.querySelector('.commit-sha').textContent.trim()).toBe('12345678'); + }); - expect(vm.$emit).toHaveBeenCalledWith('linkclicked', theFile); - }); + it('renders ID next to submodule name', () => { + expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678'); }); }); }); diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js index a030314d749..031f2a9c0b2 100644 --- a/spec/javascripts/repo/components/repo_loading_file_spec.js +++ b/spec/javascripts/repo/components/repo_loading_file_spec.js @@ -1,12 +1,16 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoLoadingFile from '~/repo/components/repo_loading_file.vue'; +import { resetStore } from '../helpers'; describe('RepoLoadingFile', () => { - function createComponent(propsData) { + let vm; + + function createComponent() { const RepoLoadingFile = Vue.extend(repoLoadingFile); return new RepoLoadingFile({ - propsData, + store, }).$mount(); } @@ -28,52 +32,31 @@ describe('RepoLoadingFile', () => { }); } - it('renders 3 columns of animated LoC', () => { - const vm = createComponent({ - loading: { - tree: true, - }, - hasFiles: false, - }); - const columns = [...vm.$el.querySelectorAll('td')]; + afterEach(() => { + vm.$destroy(); - expect(columns.length).toEqual(3); - assertColumns(columns); + resetStore(vm.$store); }); - it('renders 1 column of animated LoC if isMini', () => { - const vm = createComponent({ - loading: { - tree: true, - }, - hasFiles: false, - isMini: true, - }); + it('renders 3 columns of animated LoC', () => { + vm = createComponent(); const columns = [...vm.$el.querySelectorAll('td')]; - expect(columns.length).toEqual(1); + expect(columns.length).toEqual(3); assertColumns(columns); }); - it('does not render if tree is not loading', () => { - const vm = createComponent({ - loading: { - tree: false, - }, - hasFiles: false, - }); + it('renders 1 column of animated LoC if isMini', (done) => { + vm = createComponent(); + vm.$store.state.openFiles.push('test'); - expect(vm.$el.innerHTML).toBeFalsy(); - }); + vm.$nextTick(() => { + const columns = [...vm.$el.querySelectorAll('td')]; - it('does not render if hasFiles is true', () => { - const vm = createComponent({ - loading: { - tree: true, - }, - hasFiles: true, - }); + expect(columns.length).toEqual(1); + assertColumns(columns); - expect(vm.$el.innerHTML).toBeFalsy(); + done(); + }); }); }); diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js index 34dde545e6a..7f82ae36a64 100644 --- a/spec/javascripts/repo/components/repo_prev_directory_spec.js +++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js @@ -1,43 +1,45 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue'; +import { resetStore } from '../helpers'; describe('RepoPrevDirectory', () => { - function createComponent(propsData) { + let vm; + const parentLink = 'parent'; + function createComponent() { const RepoPrevDirectory = Vue.extend(repoPrevDirectory); - return new RepoPrevDirectory({ - propsData, - }).$mount(); - } - - it('renders a prev dir link', () => { - const prevUrl = 'prevUrl'; - const vm = createComponent({ - prevUrl, + const comp = new RepoPrevDirectory({ + store, }); - const link = vm.$el.querySelector('a'); - spyOn(vm, 'linkClicked'); + comp.$store.state.parentTreeUrl = parentLink; + + return comp.$mount(); + } - expect(link.href).toMatch(`/${prevUrl}`); - expect(link.textContent).toEqual('..'); + beforeEach(() => { + vm = createComponent(); + }); - link.click(); + afterEach(() => { + vm.$destroy(); - expect(vm.linkClicked).toHaveBeenCalledWith(prevUrl); + resetStore(vm.$store); }); - describe('methods', () => { - describe('linkClicked', () => { - const vm = jasmine.createSpyObj('vm', ['$emit']); + it('renders a prev dir link', () => { + const link = vm.$el.querySelector('a'); + + expect(link.href).toMatch(`/${parentLink}`); + expect(link.textContent).toEqual('...'); + }); - it('$emits linkclicked with file obj', () => { - const file = {}; + it('clicking row triggers getTreeData', () => { + spyOn(vm, 'getTreeData'); - repoPrevDirectory.methods.linkClicked.call(vm, file); + vm.$el.querySelector('td').click(); - expect(vm.$emit).toHaveBeenCalledWith('linkclicked', file); - }); - }); + expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink }); }); }); diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js index 4920cf02083..8d1a87494cf 100644 --- a/spec/javascripts/repo/components/repo_preview_spec.js +++ b/spec/javascripts/repo/components/repo_preview_spec.js @@ -1,23 +1,37 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoPreview from '~/repo/components/repo_preview.vue'; -import RepoStore from '~/repo/stores/repo_store'; +import { file, resetStore } from '../helpers'; describe('RepoPreview', () => { + let vm; + function createComponent() { + const f = file(); const RepoPreview = Vue.extend(repoPreview); - return new RepoPreview().$mount(); + const comp = new RepoPreview({ + store, + }); + + f.active = true; + f.html = 'test'; + + comp.$store.state.openFiles.push(f); + + return comp.$mount(); } - it('renders a div with the activeFile html', () => { - const activeFile = { - html: '<p class="file-content">html</p>', - }; - RepoStore.activeFile = activeFile; + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); - const vm = createComponent(); + it('renders a div with the activeFile html', () => { + vm = createComponent(); expect(vm.$el.tagName).toEqual('DIV'); - expect(vm.$el.innerHTML).toContain(activeFile.html); + expect(vm.$el.innerHTML).toContain('test'); }); }); diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js index abcff8e537e..7cb4dace491 100644 --- a/spec/javascripts/repo/components/repo_sidebar_spec.js +++ b/spec/javascripts/repo/components/repo_sidebar_spec.js @@ -1,111 +1,75 @@ import Vue from 'vue'; -import Helper from '~/repo/helpers/repo_helper'; -import RepoService from '~/repo/services/repo_service'; -import RepoStore from '~/repo/stores/repo_store'; +import store from '~/repo/stores'; import repoSidebar from '~/repo/components/repo_sidebar.vue'; +import { file, resetStore } from '../helpers'; describe('RepoSidebar', () => { - function createComponent() { + let vm; + + beforeEach(() => { const RepoSidebar = Vue.extend(repoSidebar); - return new RepoSidebar().$mount(); - } + vm = new RepoSidebar({ + store, + }); + + vm.$store.state.isRoot = true; + vm.$store.state.tree.push(file()); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); it('renders a sidebar', () => { - RepoStore.files = [{ - id: 0, - }]; - RepoStore.openedFiles = []; - const vm = createComponent(); const thead = vm.$el.querySelector('thead'); const tbody = vm.$el.querySelector('tbody'); expect(vm.$el.id).toEqual('sidebar'); expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); - expect(thead.querySelector('.name').textContent).toEqual('Name'); - expect(thead.querySelector('.last-commit').textContent).toEqual('Last Commit'); - expect(thead.querySelector('.last-update').textContent).toEqual('Last Update'); + expect(thead.querySelector('.name').textContent.trim()).toEqual('Name'); + expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit'); + expect(thead.querySelector('.last-update').textContent.trim()).toEqual('Last update'); expect(tbody.querySelector('.repo-file-options')).toBeFalsy(); expect(tbody.querySelector('.prev-directory')).toBeFalsy(); expect(tbody.querySelector('.loading-file')).toBeFalsy(); expect(tbody.querySelector('.file')).toBeTruthy(); }); - it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', () => { - RepoStore.openedFiles = [{ - id: 0, - }]; - const vm = createComponent(); - - expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy(); - expect(vm.$el.querySelector('thead')).toBeFalsy(); - expect(vm.$el.querySelector('tbody .repo-file-options')).toBeTruthy(); - }); + it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', (done) => { + vm.$store.state.openFiles.push(vm.$store.state.tree[0]); - it('renders 5 loading files if tree is loading and not hasFiles', () => { - RepoStore.loading = { - tree: true, - }; - RepoStore.files = []; - const vm = createComponent(); + Vue.nextTick(() => { + expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy(); + expect(vm.$el.querySelector('thead')).toBeTruthy(); + expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy(); - expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); + done(); + }); }); - it('renders a prev directory if isRoot', () => { - RepoStore.files = [{ - id: 0, - }]; - RepoStore.isRoot = true; - const vm = createComponent(); + it('renders 5 loading files if tree is loading', (done) => { + vm.$store.state.tree = []; + vm.$store.state.loading = true; - expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); - }); + Vue.nextTick(() => { + expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); - describe('methods', () => { - describe('fileClicked', () => { - it('should fetch data for new file', () => { - spyOn(Helper, 'getContent').and.callThrough(); - const file1 = { - id: 0, - url: '', - }; - RepoStore.files = [file1]; - RepoStore.isRoot = true; - const vm = createComponent(); - - vm.fileClicked(file1); - - expect(Helper.getContent).toHaveBeenCalledWith(file1); - }); - - it('should hide files in directory if already open', () => { - spyOn(RepoStore, 'removeChildFilesOfTree').and.callThrough(); - const file1 = { - id: 0, - type: 'tree', - url: '', - opened: true, - }; - RepoStore.files = [file1]; - RepoStore.isRoot = true; - const vm = createComponent(); - - vm.fileClicked(file1); - - expect(RepoStore.removeChildFilesOfTree).toHaveBeenCalledWith(file1); - }); + done(); }); + }); - describe('goToPreviousDirectoryClicked', () => { - it('should hide files in directory if already open', () => { - const prevUrl = 'foo/bar'; - const vm = createComponent(); + it('renders a prev directory if is not root', (done) => { + vm.$store.state.isRoot = false; - vm.goToPreviousDirectoryClicked(prevUrl); + Vue.nextTick(() => { + expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); - expect(RepoService.url).toEqual(prevUrl); - }); + done(); }); }); }); diff --git a/spec/javascripts/repo/components/repo_spec.js b/spec/javascripts/repo/components/repo_spec.js new file mode 100644 index 00000000000..b32d2c13af8 --- /dev/null +++ b/spec/javascripts/repo/components/repo_spec.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import store from '~/repo/stores'; +import repo from '~/repo/components/repo.vue'; +import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { file, resetStore } from '../helpers'; + +describe('repo component', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(repo); + + vm = createComponentWithStore(Component, store).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('does not render panel right when no files open', () => { + expect(vm.$el.querySelector('.panel-right')).toBeNull(); + }); + + it('renders panel right when files are open', (done) => { + vm.$store.state.tree.push(file()); + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.panel-right')).toBeNull(); + + done(); + }); + }); +}); diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js index d2a790ad73a..df0ca55aafc 100644 --- a/spec/javascripts/repo/components/repo_tab_spec.js +++ b/spec/javascripts/repo/components/repo_tab_spec.js @@ -1,69 +1,107 @@ import Vue from 'vue'; +import store from '~/repo/stores'; import repoTab from '~/repo/components/repo_tab.vue'; +import { file, resetStore } from '../helpers'; describe('RepoTab', () => { + let vm; + function createComponent(propsData) { const RepoTab = Vue.extend(repoTab); return new RepoTab({ + store, propsData, }).$mount(); } + afterEach(() => { + resetStore(vm.$store); + }); + it('renders a close link and a name link', () => { - const tab = { - url: 'url', - name: 'name', - }; - const vm = createComponent({ - tab, + vm = createComponent({ + tab: file(), }); - const close = vm.$el.querySelector('.close'); - const name = vm.$el.querySelector(`a[title="${tab.url}"]`); - - spyOn(vm, 'closeTab'); - spyOn(vm, 'tabClicked'); + vm.$store.state.openFiles.push(vm.tab); + const close = vm.$el.querySelector('.close-btn'); + const name = vm.$el.querySelector(`a[title="${vm.tab.url}"]`); expect(close.querySelector('.fa-times')).toBeTruthy(); - expect(name.textContent.trim()).toEqual(tab.name); + expect(name.textContent.trim()).toEqual(vm.tab.name); + }); + + it('calls setFileActive when clicking tab', () => { + vm = createComponent({ + tab: file(), + }); + + spyOn(vm, 'setFileActive'); - close.click(); - name.click(); + vm.$el.click(); - expect(vm.closeTab).toHaveBeenCalledWith(tab); - expect(vm.tabClicked).toHaveBeenCalledWith(tab); + expect(vm.setFileActive).toHaveBeenCalledWith(vm.tab); + }); + + it('calls closeFile when clicking close button', () => { + vm = createComponent({ + tab: file(), + }); + + spyOn(vm, 'closeFile'); + + vm.$el.querySelector('.close-btn').click(); + + expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab }); }); it('renders an fa-circle icon if tab is changed', () => { - const tab = { - url: 'url', - name: 'name', - changed: true, - }; - const vm = createComponent({ + const tab = file(); + tab.changed = true; + vm = createComponent({ tab, }); - expect(vm.$el.querySelector('.close .fa-circle')).toBeTruthy(); + expect(vm.$el.querySelector('.close-btn .fa-circle')).toBeTruthy(); }); describe('methods', () => { describe('closeTab', () => { - const vm = jasmine.createSpyObj('vm', ['$emit']); + it('does not close tab if is changed', (done) => { + const tab = file(); + tab.changed = true; + tab.opened = true; + vm = createComponent({ + tab, + }); + vm.$store.state.openFiles.push(tab); + vm.$store.dispatch('setFileActive', tab); + + vm.$el.querySelector('.close-btn').click(); - it('returns undefined and does not $emit if file is changed', () => { - const file = { changed: true }; - const returnVal = repoTab.methods.closeTab.call(vm, file); + vm.$nextTick(() => { + expect(tab.opened).toBeTruthy(); - expect(returnVal).toBeUndefined(); - expect(vm.$emit).not.toHaveBeenCalled(); + done(); + }); }); - it('$emits tabclosed event with file obj', () => { - const file = { changed: false }; - repoTab.methods.closeTab.call(vm, file); + it('closes tab when clicking close btn', (done) => { + const tab = file('lose'); + tab.opened = true; + vm = createComponent({ + tab, + }); + vm.$store.state.openFiles.push(tab); + vm.$store.dispatch('setFileActive', tab); + + vm.$el.querySelector('.close-btn').click(); + + vm.$nextTick(() => { + expect(tab.opened).toBeFalsy(); - expect(vm.$emit).toHaveBeenCalledWith('tabclosed', file); + done(); + }); }); }); }); diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js index a02b54efafc..d0246cc72e6 100644 --- a/spec/javascripts/repo/components/repo_tabs_spec.js +++ b/spec/javascripts/repo/components/repo_tabs_spec.js @@ -1,45 +1,38 @@ import Vue from 'vue'; -import RepoStore from '~/repo/stores/repo_store'; +import store from '~/repo/stores'; import repoTabs from '~/repo/components/repo_tabs.vue'; +import { file, resetStore } from '../helpers'; describe('RepoTabs', () => { - const openedFiles = [{ - id: 0, - active: true, - }, { - id: 1, - }]; + const openedFiles = [file(), file()]; + let vm; function createComponent() { const RepoTabs = Vue.extend(repoTabs); - return new RepoTabs().$mount(); + return new RepoTabs({ + store, + }).$mount(); } - it('renders a list of tabs', () => { - RepoStore.openedFiles = openedFiles; - - const vm = createComponent(); - const tabs = [...vm.$el.querySelectorAll(':scope > li')]; - - expect(vm.$el.id).toEqual('tabs'); - expect(tabs.length).toEqual(3); - expect(tabs[0].classList.contains('active')).toBeTruthy(); - expect(tabs[1].classList.contains('active')).toBeFalsy(); - expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy(); + afterEach(() => { + resetStore(vm.$store); }); - describe('methods', () => { - describe('tabClosed', () => { - it('calls removeFromOpenedFiles with file obj', () => { - const file = {}; + it('renders a list of tabs', (done) => { + vm = createComponent(); + openedFiles[0].active = true; + vm.$store.state.openFiles = openedFiles; - spyOn(RepoStore, 'removeFromOpenedFiles'); + vm.$nextTick(() => { + const tabs = [...vm.$el.querySelectorAll(':scope > li')]; - repoTabs.methods.tabClosed(file); + expect(tabs.length).toEqual(3); + expect(tabs[0].classList.contains('active')).toBeTruthy(); + expect(tabs[1].classList.contains('active')).toBeFalsy(); + expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy(); - expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file); - }); + done(); }); }); }); diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js new file mode 100644 index 00000000000..376c291c64b --- /dev/null +++ b/spec/javascripts/repo/helpers.js @@ -0,0 +1,20 @@ +import { decorateData } from '~/repo/stores/utils'; +import state from '~/repo/stores/state'; + +export const resetStore = (store) => { + store.replaceState(state()); +}; + +export const file = (name = 'name', id = name, type = '') => decorateData({ + id, + type, + icon: 'icon', + url: 'url', + name, + path: name, + last_commit: { + id: '123', + message: 'test', + committed_date: new Date().toISOString(), + }, +}); diff --git a/spec/javascripts/repo/services/repo_service_spec.js b/spec/javascripts/repo/services/repo_service_spec.js deleted file mode 100644 index 6f530770525..00000000000 --- a/spec/javascripts/repo/services/repo_service_spec.js +++ /dev/null @@ -1,171 +0,0 @@ -import axios from 'axios'; -import RepoService from '~/repo/services/repo_service'; -import RepoStore from '~/repo/stores/repo_store'; -import Api from '~/api'; - -describe('RepoService', () => { - it('has default json format param', () => { - expect(RepoService.options.params.format).toBe('json'); - }); - - describe('buildParams', () => { - let newParams; - const url = 'url'; - - beforeEach(() => { - newParams = {}; - - spyOn(Object, 'assign').and.returnValue(newParams); - }); - - it('clones params', () => { - const params = RepoService.buildParams(url); - - expect(Object.assign).toHaveBeenCalledWith({}, RepoService.options.params); - - expect(params).toBe(newParams); - }); - - it('sets and returns viewer params to richif urlIsRichBlob is true', () => { - spyOn(RepoService, 'urlIsRichBlob').and.returnValue(true); - - const params = RepoService.buildParams(url); - - expect(params.viewer).toEqual('rich'); - }); - - it('returns params urlIsRichBlob is false', () => { - spyOn(RepoService, 'urlIsRichBlob').and.returnValue(false); - - const params = RepoService.buildParams(url); - - expect(params.viewer).toBeUndefined(); - }); - - it('calls urlIsRichBlob with the objects url prop if no url arg is provided', () => { - spyOn(RepoService, 'urlIsRichBlob'); - RepoService.url = url; - - RepoService.buildParams(); - - expect(RepoService.urlIsRichBlob).toHaveBeenCalledWith(url); - }); - }); - - describe('urlIsRichBlob', () => { - it('returns true for md extension', () => { - const isRichBlob = RepoService.urlIsRichBlob('url.md'); - - expect(isRichBlob).toBeTruthy(); - }); - - it('returns false for js extension', () => { - const isRichBlob = RepoService.urlIsRichBlob('url.js'); - - expect(isRichBlob).toBeFalsy(); - }); - }); - - describe('getContent', () => { - const params = {}; - const url = 'url'; - const requestPromise = Promise.resolve(); - - beforeEach(() => { - spyOn(RepoService, 'buildParams').and.returnValue(params); - spyOn(axios, 'get').and.returnValue(requestPromise); - }); - - it('calls buildParams and axios.get', () => { - const request = RepoService.getContent(url); - - expect(RepoService.buildParams).toHaveBeenCalledWith(url); - expect(axios.get).toHaveBeenCalledWith(url, { - params, - }); - expect(request).toBe(requestPromise); - }); - - it('uses object url prop if no url arg is provided', () => { - RepoService.url = url; - - RepoService.getContent(); - - expect(axios.get).toHaveBeenCalledWith(url, { - params, - }); - }); - }); - - describe('getBase64Content', () => { - const url = 'url'; - const response = { data: 'data' }; - - beforeEach(() => { - spyOn(RepoService, 'bufferToBase64'); - spyOn(axios, 'get').and.returnValue(Promise.resolve(response)); - }); - - it('calls axios.get and bufferToBase64 on completion', (done) => { - const request = RepoService.getBase64Content(url); - - expect(axios.get).toHaveBeenCalledWith(url, { - responseType: 'arraybuffer', - }); - expect(request).toEqual(jasmine.any(Promise)); - - request.then(() => { - expect(RepoService.bufferToBase64).toHaveBeenCalledWith(response.data); - done(); - }).catch(done.fail); - }); - }); - - describe('commitFiles', () => { - it('calls commitMultiple and .then commitFlash', (done) => { - const projectId = 'projectId'; - const payload = {}; - RepoStore.projectId = projectId; - - spyOn(Api, 'commitMultiple').and.returnValue(Promise.resolve()); - spyOn(RepoService, 'commitFlash'); - - const apiPromise = RepoService.commitFiles(payload); - - expect(Api.commitMultiple).toHaveBeenCalledWith(projectId, payload); - - apiPromise.then(() => { - expect(RepoService.commitFlash).toHaveBeenCalled(); - done(); - }).catch(done.fail); - }); - }); - - describe('commitFlash', () => { - it('calls Flash with data.message', () => { - const data = { - message: 'message', - }; - spyOn(window, 'Flash'); - - RepoService.commitFlash(data); - - expect(window.Flash).toHaveBeenCalledWith(data.message); - }); - - it('calls Flash with success string if short_id and stats', () => { - const data = { - short_id: 'short_id', - stats: { - additions: '4', - deletions: '5', - }, - }; - spyOn(window, 'Flash'); - - RepoService.commitFlash(data); - - expect(window.Flash).toHaveBeenCalledWith(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice'); - }); - }); -}); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index f2072a6f350..5505f983d71 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -32,56 +32,86 @@ import '~/right_sidebar'; }; describe('RightSidebar', function() { - var fixtureName = 'issues/open-issue.html.raw'; - preloadFixtures(fixtureName); - loadJSONFixtures('todos/todos.json'); - - beforeEach(function() { - loadFixtures(fixtureName); - this.sidebar = new Sidebar; - $aside = $('.right-sidebar'); - $page = $('.page-with-sidebar'); - $icon = $aside.find('i'); - $toggle = $aside.find('.js-sidebar-toggle'); - return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); - }); - it('should expand/collapse the sidebar when arrow is clicked', function() { - assertSidebarState('expanded'); - $toggle.click(); - assertSidebarState('collapsed'); - $toggle.click(); - assertSidebarState('expanded'); - }); - it('should float over the page and when sidebar icons clicked', function() { - $labelsIcon.click(); - return assertSidebarState('expanded'); - }); - it('should collapse when the icon arrow clicked while it is floating on page', function() { - $labelsIcon.click(); - assertSidebarState('expanded'); - $toggle.click(); - return assertSidebarState('collapsed'); + describe('fixture tests', () => { + var fixtureName = 'issues/open-issue.html.raw'; + preloadFixtures(fixtureName); + loadJSONFixtures('todos/todos.json'); + + beforeEach(function() { + loadFixtures(fixtureName); + this.sidebar = new Sidebar; + $aside = $('.right-sidebar'); + $page = $('.page-with-sidebar'); + $icon = $aside.find('i'); + $toggle = $aside.find('.js-sidebar-toggle'); + return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); + }); + it('should expand/collapse the sidebar when arrow is clicked', function() { + assertSidebarState('expanded'); + $toggle.click(); + assertSidebarState('collapsed'); + $toggle.click(); + assertSidebarState('expanded'); + }); + it('should float over the page and when sidebar icons clicked', function() { + $labelsIcon.click(); + return assertSidebarState('expanded'); + }); + it('should collapse when the icon arrow clicked while it is floating on page', function() { + $labelsIcon.click(); + assertSidebarState('expanded'); + $toggle.click(); + return assertSidebarState('collapsed'); + }); + + it('should broadcast todo:toggle event when add todo clicked', function() { + var todos = getJSONFixture('todos/todos.json'); + spyOn(jQuery, 'ajax').and.callFake(function() { + var d = $.Deferred(); + var response = todos; + d.resolve(response); + return d.promise(); + }); + + var todoToggleSpy = spyOnEvent(document, 'todo:toggle'); + + $('.issuable-sidebar-header .js-issuable-todo').click(); + + expect(todoToggleSpy.calls.count()).toEqual(1); + }); + + it('should not hide collapsed icons', () => { + [].forEach.call(document.querySelectorAll('.sidebar-collapsed-icon'), (el) => { + expect(el.querySelector('.fa, svg').classList.contains('hidden')).toBeFalsy(); + }); + }); }); - it('should broadcast todo:toggle event when add todo clicked', function() { - var todos = getJSONFixture('todos/todos.json'); - spyOn(jQuery, 'ajax').and.callFake(function() { - var d = $.Deferred(); - var response = todos; - d.resolve(response); - return d.promise(); + describe('sidebarToggleClicked', () => { + const event = jasmine.createSpyObj('event', ['preventDefault']); + + beforeEach(() => { + spyOn($.fn, 'hasClass').and.returnValue(false); + }); + + afterEach(() => { + gl.lazyLoader = undefined; }); - var todoToggleSpy = spyOnEvent(document, 'todo:toggle'); + it('calls loadCheck if lazyLoader is set', () => { + gl.lazyLoader = jasmine.createSpyObj('lazyLoader', ['loadCheck']); - $('.issuable-sidebar-header .js-issuable-todo').click(); + Sidebar.prototype.sidebarToggleClicked(event); - expect(todoToggleSpy.calls.count()).toEqual(1); - }); + expect(gl.lazyLoader.loadCheck).toHaveBeenCalled(); + }); + + it('does not throw if lazyLoader is not defined', () => { + gl.lazyLoader = undefined; + + const toggle = Sidebar.prototype.sidebarToggleClicked.bind(null, event); - it('should not hide collapsed icons', () => { - [].forEach.call(document.querySelectorAll('.sidebar-collapsed-icon'), (el) => { - expect(el.querySelector('.fa, svg').classList.contains('hidden')).toBeFalsy(); + expect(toggle).not.toThrow(); }); }); }); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index a53f58b5d0d..5e55a5d2686 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -3,10 +3,9 @@ import '~/gl_dropdown'; import '~/search_autocomplete'; import '~/lib/utils/common_utils'; -import 'vendor/fuzzaldrin-plus'; (function() { - var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; + var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; var userName = 'root'; widget = null; @@ -29,25 +28,31 @@ import 'vendor/fuzzaldrin-plus'; groupName = 'Gitlab Org'; + const removeBodyAttributes = function() { + const $body = $('body'); + + $body.removeAttr('data-page'); + $body.removeAttr('data-project'); + $body.removeAttr('data-group'); + }; + // Add required attributes to body before starting the test. // section would be dashboard|group|project - addBodyAttributes = function(section) { - var $body; + const addBodyAttributes = function(section) { if (section == null) { section = 'dashboard'; } - $body = $('body'); - $body.removeAttr('data-page'); - $body.removeAttr('data-project'); - $body.removeAttr('data-group'); + + const $body = $('body'); + removeBodyAttributes(); switch (section) { case 'dashboard': - return $body.data('page', 'root:index'); + return $body.attr('data-page', 'root:index'); case 'group': - $body.data('page', 'groups:show'); + $body.attr('data-page', 'groups:show'); return $body.data('group', 'gitlab-org'); case 'project': - $body.data('page', 'projects:show'); + $body.attr('data-page', 'projects:show'); return $body.data('project', 'gitlab-ce'); } }; @@ -108,7 +113,7 @@ import 'vendor/fuzzaldrin-plus'; preloadFixtures('static/search_autocomplete.html.raw'); beforeEach(function() { loadFixtures('static/search_autocomplete.html.raw'); - widget = new gl.SearchAutocomplete; + // Prevent turbolinks from triggering within gl_dropdown spyOn(window.gl.utils, 'visitUrl').and.returnValue(true); @@ -120,6 +125,8 @@ import 'vendor/fuzzaldrin-plus'; }); afterEach(function() { + // Undo what we did to the shared <body> + removeBodyAttributes(); window.gon = {}; }); it('should show Dashboard specific dropdown menu', function() { diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index a912e150e9b..f6320db8dc4 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -1,7 +1,5 @@ -/* global ShortcutsIssuable */ - import '~/copy_as_gfm'; -import '~/shortcuts_issuable'; +import ShortcutsIssuable from '~/shortcuts_issuable'; describe('ShortcutsIssuable', () => { const fixtureName = 'merge_requests/diff_comment.html.raw'; diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js index 53e4c68beb3..a2a609edef9 100644 --- a/spec/javascripts/shortcuts_spec.js +++ b/spec/javascripts/shortcuts_spec.js @@ -1,4 +1,5 @@ -/* global Shortcuts */ +import Shortcuts from '~/shortcuts'; + describe('Shortcuts', () => { const fixtureName = 'merge_requests/diff_comment.html.raw'; const createEvent = (type, target) => $.Event(type, { @@ -8,19 +9,17 @@ describe('Shortcuts', () => { preloadFixtures(fixtureName); describe('toggleMarkdownPreview', () => { - let sc; - beforeEach(() => { loadFixtures(fixtureName); spyOnEvent('.js-new-note-form .js-md-preview-button', 'focus'); spyOnEvent('.edit-note .js-md-preview-button', 'focus'); - sc = new Shortcuts(); + new Shortcuts(); // eslint-disable-line no-new }); it('focuses preview button in form', () => { - sc.toggleMarkdownPreview( + Shortcuts.toggleMarkdownPreview( createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text'), )); @@ -31,7 +30,7 @@ describe('Shortcuts', () => { document.querySelector('.js-note-edit').click(); setTimeout(() => { - sc.toggleMarkdownPreview( + Shortcuts.toggleMarkdownPreview( createEvent('KeyboardEvent', document.querySelector('.edit-note .js-note-text'), )); diff --git a/spec/javascripts/sidebar/lock/edit_form_buttons_spec.js b/spec/javascripts/sidebar/lock/edit_form_buttons_spec.js new file mode 100644 index 00000000000..b0ea8ae0206 --- /dev/null +++ b/spec/javascripts/sidebar/lock/edit_form_buttons_spec.js @@ -0,0 +1,36 @@ +import Vue from 'vue'; +import editFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('EditFormButtons', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editFormButtons); + const toggleForm = () => { }; + const updateLockedAttribute = () => { }; + + vm1 = mountComponent(Component, { + isLocked: true, + toggleForm, + updateLockedAttribute, + }); + + vm2 = mountComponent(Component, { + isLocked: false, + toggleForm, + updateLockedAttribute, + }); + }); + + it('renders unlock or lock text based on locked state', () => { + expect( + vm1.$el.innerHTML.includes('Unlock'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('Lock'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/lock/edit_form_spec.js b/spec/javascripts/sidebar/lock/edit_form_spec.js new file mode 100644 index 00000000000..7abd6997a18 --- /dev/null +++ b/spec/javascripts/sidebar/lock/edit_form_spec.js @@ -0,0 +1,41 @@ +import Vue from 'vue'; +import editForm from '~/sidebar/components/lock/edit_form.vue'; + +describe('EditForm', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(editForm); + const toggleForm = () => { }; + const updateLockedAttribute = () => { }; + + vm1 = new Component({ + propsData: { + isLocked: true, + toggleForm, + updateLockedAttribute, + issuableType: 'issue', + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isLocked: false, + toggleForm, + updateLockedAttribute, + issuableType: 'merge_request', + }, + }).$mount(); + }); + + it('renders on the appropriate warning text', () => { + expect( + vm1.$el.innerHTML.includes('Unlock this issue?'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('Lock this merge request?'), + ).toBe(true); + }); +}); diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js new file mode 100644 index 00000000000..696fca516bc --- /dev/null +++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js @@ -0,0 +1,71 @@ +import Vue from 'vue'; +import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue'; + +describe('LockIssueSidebar', () => { + let vm1; + let vm2; + + beforeEach(() => { + const Component = Vue.extend(lockIssueSidebar); + + const mediator = { + service: { + update: Promise.resolve(true), + }, + + store: { + isLockDialogOpen: false, + }, + }; + + vm1 = new Component({ + propsData: { + isLocked: true, + isEditable: true, + mediator, + issuableType: 'issue', + }, + }).$mount(); + + vm2 = new Component({ + propsData: { + isLocked: false, + isEditable: false, + mediator, + issuableType: 'merge_request', + }, + }).$mount(); + }); + + it('shows if locked and/or editable', () => { + expect( + vm1.$el.innerHTML.includes('Edit'), + ).toBe(true); + + expect( + vm1.$el.innerHTML.includes('Locked'), + ).toBe(true); + + expect( + vm2.$el.innerHTML.includes('Unlocked'), + ).toBe(true); + }); + + it('displays the edit form when editable', (done) => { + expect(vm1.isLockDialogOpen).toBe(false); + + vm1.$el.querySelector('.lock-edit').click(); + + expect(vm1.isLockDialogOpen).toBe(true); + + vm1.$nextTick(() => { + expect( + vm1.$el + .innerHTML + .includes('Unlock this issue?'), + ).toBe(true); + + done(); + }); + }); +}); diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index e2b6bcabc98..0682b463043 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -109,12 +109,14 @@ const sidebarMockData = { labels: [], web_url: '/root/some-project/issues/5', }, + '/gitlab-org/gitlab-shell/issues/5/toggle_subscription': {}, }, }; export default { mediator: { endpoint: '/gitlab-org/gitlab-shell/issues/5.json', + toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', editable: true, diff --git a/spec/javascripts/sidebar/participants_spec.js b/spec/javascripts/sidebar/participants_spec.js new file mode 100644 index 00000000000..30cc549c7c0 --- /dev/null +++ b/spec/javascripts/sidebar/participants_spec.js @@ -0,0 +1,174 @@ +import Vue from 'vue'; +import participants from '~/sidebar/components/participants/participants.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +const PARTICIPANT = { + id: 1, + state: 'active', + username: 'marcene', + name: 'Allie Will', + web_url: 'foo.com', + avatar_url: 'gravatar.com/avatar/xxx', +}; + +const PARTICIPANT_LIST = [ + PARTICIPANT, + { ...PARTICIPANT, id: 2 }, + { ...PARTICIPANT, id: 3 }, +]; + +describe('Participants', function () { + let vm; + let Participants; + + beforeEach(() => { + Participants = Vue.extend(participants); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('collapsed sidebar state', () => { + it('shows loading spinner when loading', () => { + vm = mountComponent(Participants, { + loading: true, + }); + + expect(vm.$el.querySelector('.js-participants-collapsed-loading-icon')).toBeDefined(); + }); + + it('shows participant count when given', () => { + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + }); + const countEl = vm.$el.querySelector('.js-participants-collapsed-count'); + + expect(countEl.textContent.trim()).toBe(`${PARTICIPANT_LIST.length}`); + }); + + it('shows full participant count when there are hidden participants', () => { + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants: 1, + }); + const countEl = vm.$el.querySelector('.js-participants-collapsed-count'); + + expect(countEl.textContent.trim()).toBe(`${PARTICIPANT_LIST.length}`); + }); + }); + + describe('expanded sidebar state', () => { + it('shows loading spinner when loading', () => { + vm = mountComponent(Participants, { + loading: true, + }); + + expect(vm.$el.querySelector('.js-participants-expanded-loading-icon')).toBeDefined(); + }); + + it('when only showing visible participants, shows an avatar only for each participant under the limit', (done) => { + const numberOfLessParticipants = 2; + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants, + }); + vm.isShowingMoreParticipants = false; + + Vue.nextTick() + .then(() => { + const participantEls = vm.$el.querySelectorAll('.js-participants-author'); + + expect(participantEls.length).toBe(numberOfLessParticipants); + }) + .then(done) + .catch(done.fail); + }); + + it('when only showing all participants, each has an avatar', (done) => { + const numberOfLessParticipants = 2; + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants, + }); + vm.isShowingMoreParticipants = true; + + Vue.nextTick() + .then(() => { + const participantEls = vm.$el.querySelectorAll('.js-participants-author'); + + expect(participantEls.length).toBe(PARTICIPANT_LIST.length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not have more participants link when they can all be shown', () => { + const numberOfLessParticipants = 100; + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants, + }); + const moreParticipantLink = vm.$el.querySelector('.js-toggle-participants-button'); + + expect(PARTICIPANT_LIST.length).toBeLessThan(numberOfLessParticipants); + expect(moreParticipantLink).toBeNull(); + }); + + it('when too many participants, has more participants link to show more', (done) => { + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants: 2, + }); + vm.isShowingMoreParticipants = false; + + Vue.nextTick() + .then(() => { + const moreParticipantLink = vm.$el.querySelector('.js-toggle-participants-button'); + + expect(moreParticipantLink.textContent.trim()).toBe('+ 1 more'); + }) + .then(done) + .catch(done.fail); + }); + + it('when too many participants and already showing them, has more participants link to show less', (done) => { + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants: 2, + }); + vm.isShowingMoreParticipants = true; + + Vue.nextTick() + .then(() => { + const moreParticipantLink = vm.$el.querySelector('.js-toggle-participants-button'); + + expect(moreParticipantLink.textContent.trim()).toBe('- show less'); + }) + .then(done) + .catch(done.fail); + }); + + it('clicking more participants link emits event', () => { + vm = mountComponent(Participants, { + loading: false, + participants: PARTICIPANT_LIST, + numberOfLessParticipants: 2, + }); + const moreParticipantLink = vm.$el.querySelector('.js-toggle-participants-button'); + + expect(vm.isShowingMoreParticipants).toBe(false); + + moreParticipantLink.click(); + + expect(vm.isShowingMoreParticipants).toBe(true); + }); + }); +}); diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js index 3aa8ca5db0d..7deb1fd2118 100644 --- a/spec/javascripts/sidebar/sidebar_mediator_spec.js +++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js @@ -57,8 +57,8 @@ describe('Sidebar mediator', () => { .then(() => { expect(this.mediator.service.getProjectsAutocomplete).toHaveBeenCalledWith(searchTerm); expect(this.mediator.store.setAutocompleteProjects).toHaveBeenCalled(); - done(); }) + .then(done) .catch(done.fail); }); @@ -72,8 +72,21 @@ describe('Sidebar mediator', () => { .then(() => { expect(this.mediator.service.moveIssue).toHaveBeenCalledWith(moveToProjectId); expect(gl.utils.visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5'); - done(); }) + .then(done) + .catch(done.fail); + }); + + it('toggle subscription', (done) => { + this.mediator.store.setSubscribedState(false); + spyOn(this.mediator.service, 'toggleSubscription').and.callThrough(); + + this.mediator.toggleSubscription() + .then(() => { + expect(this.mediator.service.toggleSubscription).toHaveBeenCalled(); + expect(this.mediator.store.subscribed).toEqual(true); + }) + .then(done) .catch(done.fail); }); }); diff --git a/spec/javascripts/sidebar/sidebar_service_spec.js b/spec/javascripts/sidebar/sidebar_service_spec.js index a4bd8ba8d88..7324d34d84a 100644 --- a/spec/javascripts/sidebar/sidebar_service_spec.js +++ b/spec/javascripts/sidebar/sidebar_service_spec.js @@ -7,6 +7,7 @@ describe('Sidebar service', () => { Vue.http.interceptors.push(Mock.sidebarMockInterceptor); this.service = new SidebarService({ endpoint: '/gitlab-org/gitlab-shell/issues/5.json', + toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', }); @@ -23,6 +24,7 @@ describe('Sidebar service', () => { expect(resp).toBeDefined(); done(); }) + .then(done) .catch(done.fail); }); @@ -30,8 +32,8 @@ describe('Sidebar service', () => { this.service.update('issue[assignee_ids]', [1]) .then((resp) => { expect(resp).toBeDefined(); - done(); }) + .then(done) .catch(done.fail); }); @@ -39,8 +41,8 @@ describe('Sidebar service', () => { this.service.getProjectsAutocomplete() .then((resp) => { expect(resp).toBeDefined(); - done(); }) + .then(done) .catch(done.fail); }); @@ -48,8 +50,17 @@ describe('Sidebar service', () => { this.service.moveIssue(123) .then((resp) => { expect(resp).toBeDefined(); - done(); }) + .then(done) + .catch(done.fail); + }); + + it('toggles the subscription', (done) => { + this.service.toggleSubscription() + .then((resp) => { + expect(resp).toBeDefined(); + }) + .then(done) .catch(done.fail); }); }); diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js index 69eb3839d67..51dee64fb93 100644 --- a/spec/javascripts/sidebar/sidebar_store_spec.js +++ b/spec/javascripts/sidebar/sidebar_store_spec.js @@ -2,21 +2,36 @@ import SidebarStore from '~/sidebar/stores/sidebar_store'; import Mock from './mock_data'; import UsersMockHelper from '../helpers/user_mock_data_helper'; -describe('Sidebar store', () => { - const assignee = { - id: 2, - name: 'gitlab user 2', - username: 'gitlab2', - avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }; - - const anotherAssignee = { - id: 3, - name: 'gitlab user 3', - username: 'gitlab3', - avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }; +const ASSIGNEE = { + id: 2, + name: 'gitlab user 2', + username: 'gitlab2', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', +}; + +const ANOTHER_ASSINEE = { + id: 3, + name: 'gitlab user 3', + username: 'gitlab3', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', +}; + +const PARTICIPANT = { + id: 1, + state: 'active', + username: 'marcene', + name: 'Allie Will', + web_url: 'foo.com', + avatar_url: 'gravatar.com/avatar/xxx', +}; + +const PARTICIPANT_LIST = [ + PARTICIPANT, + { ...PARTICIPANT, id: 2 }, + { ...PARTICIPANT, id: 3 }, +]; +describe('Sidebar store', () => { beforeEach(() => { this.store = new SidebarStore({ currentUser: { @@ -40,23 +55,23 @@ describe('Sidebar store', () => { }); it('adds a new assignee', () => { - this.store.addAssignee(assignee); + this.store.addAssignee(ASSIGNEE); expect(this.store.assignees.length).toEqual(1); }); it('removes an assignee', () => { - this.store.removeAssignee(assignee); + this.store.removeAssignee(ASSIGNEE); expect(this.store.assignees.length).toEqual(0); }); it('finds an existent assignee', () => { let foundAssignee; - this.store.addAssignee(assignee); - foundAssignee = this.store.findAssignee(assignee); + this.store.addAssignee(ASSIGNEE); + foundAssignee = this.store.findAssignee(ASSIGNEE); expect(foundAssignee).toBeDefined(); - expect(foundAssignee).toEqual(assignee); - foundAssignee = this.store.findAssignee(anotherAssignee); + expect(foundAssignee).toEqual(ASSIGNEE); + foundAssignee = this.store.findAssignee(ANOTHER_ASSINEE); expect(foundAssignee).toBeUndefined(); }); @@ -65,6 +80,28 @@ describe('Sidebar store', () => { expect(this.store.assignees.length).toEqual(0); }); + it('sets participants data', () => { + expect(this.store.participants.length).toEqual(0); + + this.store.setParticipantsData({ + participants: PARTICIPANT_LIST, + }); + + expect(this.store.isFetching.participants).toEqual(false); + expect(this.store.participants.length).toEqual(PARTICIPANT_LIST.length); + }); + + it('sets subcriptions data', () => { + expect(this.store.subscribed).toEqual(null); + + this.store.setSubscriptionsData({ + subscribed: true, + }); + + expect(this.store.isFetching.subscriptions).toEqual(false); + expect(this.store.subscribed).toEqual(true); + }); + it('set assigned data', () => { const users = { assignees: UsersMockHelper.createNumberRandomUsers(3), @@ -75,6 +112,14 @@ describe('Sidebar store', () => { expect(this.store.assignees.length).toEqual(3); }); + it('sets fetching state', () => { + expect(this.store.isFetching.participants).toEqual(true); + + this.store.setFetchingState('participants', false); + + expect(this.store.isFetching.participants).toEqual(false); + }); + it('set time tracking data', () => { this.store.setTimeTrackingData(Mock.time); expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate); @@ -90,6 +135,14 @@ describe('Sidebar store', () => { expect(this.store.autocompleteProjects).toEqual(projects); }); + it('sets subscribed state', () => { + expect(this.store.subscribed).toEqual(null); + + this.store.setSubscribedState(true); + + expect(this.store.subscribed).toEqual(true); + }); + it('set move to project ID', () => { const projectId = 7; this.store.setMoveToProjectId(projectId); diff --git a/spec/javascripts/sidebar/sidebar_subscriptions_spec.js b/spec/javascripts/sidebar/sidebar_subscriptions_spec.js new file mode 100644 index 00000000000..7adf22b0f1f --- /dev/null +++ b/spec/javascripts/sidebar/sidebar_subscriptions_spec.js @@ -0,0 +1,36 @@ +import Vue from 'vue'; +import sidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_subscriptions.vue'; +import SidebarMediator from '~/sidebar/sidebar_mediator'; +import SidebarService from '~/sidebar/services/sidebar_service'; +import SidebarStore from '~/sidebar/stores/sidebar_store'; +import eventHub from '~/sidebar/event_hub'; +import mountComponent from '../helpers/vue_mount_component_helper'; +import Mock from './mock_data'; + +describe('Sidebar Subscriptions', function () { + let vm; + let SidebarSubscriptions; + + beforeEach(() => { + SidebarSubscriptions = Vue.extend(sidebarSubscriptions); + // Setup the stores, services, etc + // eslint-disable-next-line no-new + new SidebarMediator(Mock.mediator); + }); + + afterEach(() => { + vm.$destroy(); + SidebarService.singleton = null; + SidebarStore.singleton = null; + SidebarMediator.singleton = null; + }); + + it('calls the mediator toggleSubscription on event', () => { + spyOn(SidebarMediator.prototype, 'toggleSubscription').and.returnValue(Promise.resolve()); + vm = mountComponent(SidebarSubscriptions, {}); + + eventHub.$emit('toggleSubscription'); + + expect(SidebarMediator.prototype.toggleSubscription).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js new file mode 100644 index 00000000000..9b33dd02fb9 --- /dev/null +++ b/spec/javascripts/sidebar/subscriptions_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; +import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Subscriptions', function () { + let vm; + let Subscriptions; + + beforeEach(() => { + Subscriptions = Vue.extend(subscriptions); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('shows loading spinner when loading', () => { + vm = mountComponent(Subscriptions, { + loading: true, + subscribed: undefined, + }); + + expect(vm.$refs.loadingButton.loading).toBe(true); + expect(vm.$refs.loadingButton.label).toBeUndefined(); + }); + + it('has "Subscribe" text when currently not subscribed', () => { + vm = mountComponent(Subscriptions, { + subscribed: false, + }); + + expect(vm.$refs.loadingButton.label).toBe('Subscribe'); + }); + + it('has "Unsubscribe" text when currently not subscribed', () => { + vm = mountComponent(Subscriptions, { + subscribed: true, + }); + + expect(vm.$refs.loadingButton.label).toBe('Unsubscribe'); + }); +}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index d4e134583c7..fd7aa332d17 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -11,6 +11,12 @@ const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent); Vue.config.devtools = !isHeadlessChrome; Vue.config.productionTip = false; +let hasVueWarnings = false; +Vue.config.warnHandler = (msg, vm, trace) => { + hasVueWarnings = true; + fail(`${msg}${trace}`); +}; + Vue.use(VueResource); // enable test fixtures @@ -34,11 +40,6 @@ window.addEventListener('unhandledrejection', (event) => { console.error(event.reason.stack || event.reason); }); -const checkUnhandledPromiseRejections = (done) => { - expect(hasUnhandledPromiseRejections).toBe(false); - done(); -}; - // HACK: Chrome 59 disconnects if there are too many synchronous tests in a row // because it appears to lock up the thread that communicates to Karma's socket // This async beforeEach gets called on every spec and releases the JS thread long @@ -47,17 +48,6 @@ const checkUnhandledPromiseRejections = (done) => { // to run our unit tests. beforeEach(done => done()); -beforeAll(() => { - const origError = console.error; - spyOn(console, 'error').and.callFake((message) => { - if (/^\[Vue warn\]/.test(message)) { - fail(message); - } else { - origError(message); - } - }); -}); - const builtinVueHttpInterceptors = Vue.http.interceptors.slice(); beforeEach(() => { @@ -80,8 +70,22 @@ testsContext.keys().forEach(function (path) { } }); -it('has no unhandled Promise rejections', (done) => { - setTimeout(checkUnhandledPromiseRejections(done), 1000); +describe('test errors', () => { + beforeAll((done) => { + if (hasUnhandledPromiseRejections || hasVueWarnings) { + setTimeout(done, 1000); + } else { + done(); + } + }); + + it('has no unhandled Promise rejections', () => { + expect(hasUnhandledPromiseRejections).toBe(false); + }); + + it('has no Vue warnings', () => { + expect(hasVueWarnings).toBe(false); + }); }); // if we're generating coverage reports, make sure to include all files so diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index a160c86308d..29b15f3a782 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,72 +1,63 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, max-len */ -/* global MockU2FDevice */ -/* global U2FAuthenticate */ - -import '~/u2f/authenticate'; -import '~/u2f/util'; -import '~/u2f/error'; +import U2FAuthenticate from '~/u2f/authenticate'; import 'vendor/u2f'; -import './mock_u2f_device'; +import MockU2FDevice from './mock_u2f_device'; + +describe('U2FAuthenticate', () => { + preloadFixtures('u2f/authenticate.html.raw'); -(function() { - describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html.raw'); + beforeEach(() => { + loadFixtures('u2f/authenticate.html.raw'); + this.u2fDevice = new MockU2FDevice(); + this.container = $('#js-authenticate-u2f'); + this.component = new U2FAuthenticate( + this.container, + '#js-login-u2f-form', + { + sign_requests: [], + }, + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form'), + ); - beforeEach(function() { - loadFixtures('u2f/authenticate.html.raw'); - this.u2fDevice = new MockU2FDevice; - this.container = $("#js-authenticate-u2f"); - this.component = new window.gl.U2FAuthenticate( - this.container, - '#js-login-u2f-form', - { - sign_requests: [] - }, - document.querySelector('#js-login-2fa-device'), - document.querySelector('.js-2fa-form') - ); + // bypass automatic form submission within renderAuthenticated + spyOn(this.component, 'renderAuthenticated').and.returnValue(true); - // bypass automatic form submission within renderAuthenticated - spyOn(this.component, 'renderAuthenticated').and.returnValue(true); + return this.component.start(); + }); - return this.component.start(); + it('allows authenticating via a U2F device', () => { + const inProgressMessage = this.container.find('p'); + expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); + this.u2fDevice.respondToAuthenticateRequest({ + deviceData: 'this is data from the device', }); - it('allows authenticating via a U2F device', function() { - var inProgressMessage; - inProgressMessage = this.container.find("p"); - expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); + expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); + }); + + return describe('errors', () => { + it('displays an error message', () => { + const setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); this.u2fDevice.respondToAuthenticateRequest({ - deviceData: "this is data from the device" + errorCode: 'error!', }); - expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('There was a problem communicating with your device'); }); - return describe("errors", function() { - it("displays an error message", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - errorCode: "error!" - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("There was a problem communicating with your device"); + return it('allows retrying authentication after an error', () => { + let setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToAuthenticateRequest({ + errorCode: 'error!', }); - return it("allows retrying authentication after an error", function() { - var retryButton, setupButton; - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - errorCode: "error!" - }); - retryButton = this.container.find("#js-u2f-try-again"); - retryButton.trigger('click'); - setupButton = this.container.find("#js-login-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToAuthenticateRequest({ - deviceData: "this is data from the device" - }); - expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); + const retryButton = this.container.find('#js-u2f-try-again'); + retryButton.trigger('click'); + setupButton = this.container.find('#js-login-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToAuthenticateRequest({ + deviceData: 'this is data from the device', }); + expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); }); }); -}).call(window); +}); diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 4eb8ad3d9e4..5a1ace2b4d6 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,31 +1,28 @@ -/* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */ +/* eslint-disable prefer-rest-params, wrap-iife, +no-unused-expressions, no-return-assign, no-param-reassign*/ -(function() { - this.MockU2FDevice = (function() { - function MockU2FDevice() { - this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); - this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); - window.u2f || (window.u2f = {}); - window.u2f.register = (function(_this) { - return function(appId, registerRequests, signRequests, callback) { - return _this.registerCallback = callback; - }; - })(this); - window.u2f.sign = (function(_this) { - return function(appId, challenges, signRequests, callback) { - return _this.authenticateCallback = callback; - }; - })(this); - } +export default class MockU2FDevice { + constructor() { + this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); + this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); + window.u2f || (window.u2f = {}); + window.u2f.register = (function (_this) { + return function (appId, registerRequests, signRequests, callback) { + return _this.registerCallback = callback; + }; + })(this); + window.u2f.sign = (function (_this) { + return function (appId, challenges, signRequests, callback) { + return _this.authenticateCallback = callback; + }; + })(this); + } - MockU2FDevice.prototype.respondToRegisterRequest = function(params) { - return this.registerCallback(params); - }; + respondToRegisterRequest(params) { + return this.registerCallback(params); + } - MockU2FDevice.prototype.respondToAuthenticateRequest = function(params) { - return this.authenticateCallback(params); - }; - - return MockU2FDevice; - })(); -}).call(window); + respondToAuthenticateRequest(params) { + return this.authenticateCallback(params); + } +} diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index a445c80f2af..b0051f11362 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,77 +1,69 @@ -/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, max-len */ -/* global MockU2FDevice */ -/* global U2FRegister */ - -import '~/u2f/register'; -import '~/u2f/util'; -import '~/u2f/error'; +import U2FRegister from '~/u2f/register'; import 'vendor/u2f'; -import './mock_u2f_device'; +import MockU2FDevice from './mock_u2f_device'; + +describe('U2FRegister', () => { + preloadFixtures('u2f/register.html.raw'); -(function() { - describe('U2FRegister', function() { - preloadFixtures('u2f/register.html.raw'); + beforeEach(() => { + loadFixtures('u2f/register.html.raw'); + this.u2fDevice = new MockU2FDevice(); + this.container = $('#js-register-u2f'); + this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); + return this.component.start(); + }); - beforeEach(function() { - loadFixtures('u2f/register.html.raw'); - this.u2fDevice = new MockU2FDevice; - this.container = $("#js-register-u2f"); - this.component = new U2FRegister(this.container, $("#js-register-u2f-templates"), {}, "token"); - return this.component.start(); + it('allows registering a U2F device', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); + expect(setupButton.text()).toBe('Setup new U2F device'); + setupButton.trigger('click'); + const inProgressMessage = this.container.children('p'); + expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); + this.u2fDevice.respondToRegisterRequest({ + deviceData: 'this is data from the device', }); - it('allows registering a U2F device', function() { - var deviceResponse, inProgressMessage, registeredMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - expect(setupButton.text()).toBe('Setup new U2F device'); + const registeredMessage = this.container.find('p'); + const deviceResponse = this.container.find('#js-device-response'); + expect(registeredMessage.text()).toContain('Your device was successfully set up!'); + return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); + }); + + return describe('errors', () => { + it('doesn\'t allow the same device to be registered twice (for the same user', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); setupButton.trigger('click'); - inProgressMessage = this.container.children("p"); - expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); this.u2fDevice.respondToRegisterRequest({ - deviceData: "this is data from the device" + errorCode: 4, }); - registeredMessage = this.container.find('p'); - deviceResponse = this.container.find('#js-device-response'); - expect(registeredMessage.text()).toContain("Your device was successfully set up!"); - return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('already been registered with us'); }); - return describe("errors", function() { - it("doesn't allow the same device to be registered twice (for the same user", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: 4 - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("already been registered with us"); + + it('displays an error message for other errors', () => { + const setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + errorCode: 'error!', }); - it("displays an error message for other errors", function() { - var errorMessage, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: "error!" - }); - errorMessage = this.container.find("p"); - return expect(errorMessage.text()).toContain("There was a problem communicating with your device"); + const errorMessage = this.container.find('p'); + return expect(errorMessage.text()).toContain('There was a problem communicating with your device'); + }); + + return it('allows retrying registration after an error', () => { + let setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + errorCode: 'error!', }); - return it("allows retrying registration after an error", function() { - var registeredMessage, retryButton, setupButton; - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - errorCode: "error!" - }); - retryButton = this.container.find("#U2FTryAgain"); - retryButton.trigger('click'); - setupButton = this.container.find("#js-setup-u2f-device"); - setupButton.trigger('click'); - this.u2fDevice.respondToRegisterRequest({ - deviceData: "this is data from the device" - }); - registeredMessage = this.container.find("p"); - return expect(registeredMessage.text()).toContain("Your device was successfully set up!"); + const retryButton = this.container.find('#U2FTryAgain'); + retryButton.trigger('click'); + setupButton = this.container.find('#js-setup-u2f-device'); + setupButton.trigger('click'); + this.u2fDevice.respondToRegisterRequest({ + deviceData: 'this is data from the device', }); + const registeredMessage = this.container.find('p'); + return expect(registeredMessage.text()).toContain('Your device was successfully set up!'); }); }); -}).call(window); +}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index 690665ae12c..33ed0cb4342 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import { statusIconEntityMap } from '~/vue_shared/ci_status_icons'; import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline'; import mockData from '../mock_data'; @@ -29,14 +28,6 @@ describe('MRWidgetPipeline', () => { }); describe('computed', () => { - describe('svg', () => { - it('should have the proper SVG icon', () => { - const vm = createComponent({ pipeline: mockData.pipeline }); - - expect(vm.svg).toEqual(statusIconEntityMap.icon_status_failed); - }); - }); - describe('hasPipeline', () => { it('should return true when there is a pipeline', () => { expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0); @@ -142,6 +133,7 @@ describe('MRWidgetPipeline', () => { Vue.nextTick(() => { expect(el.querySelectorAll('.js-ci-error').length).toEqual(1); expect(el.innerText).toContain('Could not connect to the CI server'); + expect(el.querySelector('.ci-status-icon svg use').getAttribute('xlink:href')).toContain('status_failed'); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js index 47303d1e80f..d23b558f4ea 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js @@ -4,11 +4,15 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid const mr = { targetBranch: 'good-branch', targetBranchPath: '/good-branch', - closedBy: { - name: 'Fatih Acet', - username: 'fatihacet', + closedEvent: { + author: { + name: 'Fatih Acet', + username: 'fatihacet', + }, + updatedAt: 'closedEventUpdatedAt', + formattedUpdatedAt: '', }, - updatedAt: '2017-03-23T20:08:08.845Z', + updatedAt: 'mrUpdatedAt', closedAt: '1 day ago', }; @@ -18,7 +22,7 @@ const createComponent = () => { return new Component({ el: document.createElement('div'), propsData: { mr }, - }).$el; + }); }; describe('MRWidgetClosed', () => { @@ -38,14 +42,30 @@ describe('MRWidgetClosed', () => { }); describe('template', () => { - it('should have correct elements', () => { - const el = createComponent(); + let vm; + let el; + beforeEach(() => { + vm = createComponent(); + el = vm.$el; + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should have correct elements', () => { expect(el.querySelector('h4').textContent).toContain('Closed by'); - expect(el.querySelector('h4').textContent).toContain(mr.closedBy.name); + expect(el.querySelector('h4').textContent).toContain(mr.closedEvent.author.name); expect(el.textContent).toContain('The changes were not merged into'); expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath); expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch); }); + + it('should use closedEvent updatedAt as tooltip title', () => { + expect( + el.querySelector('time').getAttribute('title'), + ).toBe('closedEventUpdatedAt'); + }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index 3b7b7d93662..5d4c7ec09dc 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -1,20 +1,9 @@ import Vue from 'vue'; import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; +const ConflictsComponent = Vue.extend(conflictsComponent); const path = '/conflicts'; -const createComponent = () => { - const Component = Vue.extend(conflictsComponent); - - return new Component({ - el: document.createElement('div'), - propsData: { - mr: { - canMerge: true, - conflictResolutionPath: path, - }, - }, - }); -}; describe('MRWidgetConflicts', () => { describe('props', () => { @@ -27,44 +16,90 @@ describe('MRWidgetConflicts', () => { }); describe('template', () => { - it('should have correct elements', () => { - const el = createComponent().$el; - const resolveButton = el.querySelector('.js-resolve-conflicts-button'); - const mergeButton = el.querySelector('.mr-widget-body .btn'); - const mergeLocallyButton = el.querySelector('.js-merge-locally-button'); - - expect(el.textContent).toContain('There are merge conflicts'); - expect(el.textContent).not.toContain('ask someone with write access'); - expect(el.querySelector('.btn-success').disabled).toBeTruthy(); - expect(resolveButton.textContent).toContain('Resolve conflicts'); - expect(resolveButton.getAttribute('href')).toEqual(path); - expect(mergeButton.textContent).toContain('Merge'); - expect(mergeLocallyButton.textContent).toContain('Merge locally'); + describe('when allowed to merge', () => { + let vm; + + beforeEach(() => { + vm = mountComponent(ConflictsComponent, { + mr: { + canMerge: true, + conflictResolutionPath: path, + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should tell you about conflicts without bothering other people', () => { + expect(vm.$el.textContent).toContain('There are merge conflicts'); + expect(vm.$el.textContent).not.toContain('ask someone with write access'); + }); + + it('should allow you to resolve the conflicts', () => { + const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button'); + + expect(resolveButton.textContent).toContain('Resolve conflicts'); + expect(resolveButton.getAttribute('href')).toEqual(path); + }); + + it('should have merge buttons', () => { + const mergeButton = vm.$el.querySelector('.js-disabled-merge-button'); + const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button'); + + expect(mergeButton.textContent).toContain('Merge'); + expect(mergeButton.disabled).toBeTruthy(); + expect(mergeButton.classList.contains('btn-success')).toEqual(true); + expect(mergeLocallyButton.textContent).toContain('Merge locally'); + }); }); describe('when user does not have permission to merge', () => { let vm; beforeEach(() => { - vm = createComponent(); - vm.mr.canMerge = false; + vm = mountComponent(ConflictsComponent, { + mr: { + canMerge: false, + }, + }); }); - it('should show proper message', (done) => { - Vue.nextTick(() => { - expect(vm.$el.textContent).toContain('ask someone with write access'); - done(); - }); + afterEach(() => { + vm.$destroy(); + }); + + it('should show proper message', () => { + expect(vm.$el.textContent).toContain('ask someone with write access'); + }); + + it('should not have action buttons', () => { + expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined(); + expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull(); + expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull(); }); + }); - it('should not have action buttons', (done) => { - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.btn').length).toBe(1); - expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toEqual(null); - expect(vm.$el.querySelector('.js-merge-locally-button')).toEqual(null); - done(); + describe('when fast-forward or semi-linear merge enabled', () => { + let vm; + + beforeEach(() => { + vm = mountComponent(ConflictsComponent, { + mr: { + shouldBeRebased: true, + }, }); }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should tell you to rebase locally', () => { + expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.'); + expect(vm.$el.textContent).toContain('To merge this request, first rebase locally'); + }); }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index afaa750199a..2714e8294fa 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -14,9 +14,12 @@ const createComponent = () => { canRevertInCurrentMR: true, canRemoveSourceBranch: true, sourceBranchRemoved: true, - mergedBy: {}, - mergedAt: '', - updatedAt: '', + mergedEvent: { + author: {}, + updatedAt: 'mergedUpdatedAt', + formattedUpdatedAt: '', + }, + updatedAt: 'mrUpdatedAt', targetBranch, }; @@ -170,5 +173,11 @@ describe('MRWidgetMerged', () => { done(); }); }); + + it('should use mergedEvent updatedAt as tooltip title', () => { + expect( + el.querySelector('time').getAttribute('title'), + ).toBe('mergedUpdatedAt'); + }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 03a52f1f91c..df3d29ee1f9 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -12,6 +12,7 @@ const createComponent = (customConfig = {}) => { pipeline: null, isPipelineFailed: false, isPipelinePassing: false, + isMergeAllowed: true, onlyAllowMergeIfPipelineSucceeds: false, hasCI: false, ciStatus: null, @@ -42,6 +43,10 @@ describe('MRWidgetReadyToMerge', () => { vm = createComponent(); }); + afterEach(() => { + vm.$destroy(); + }); + describe('props', () => { it('should have props', () => { const { mr, service } = readyToMergeComponent.props; @@ -95,35 +100,84 @@ describe('MRWidgetReadyToMerge', () => { }); }); + describe('status', () => { + it('defaults to success', () => { + vm.mr.pipeline = true; + expect(vm.status).toEqual('success'); + }); + + it('returns failed when MR has CI but also has an unknown status', () => { + vm.mr.hasCI = true; + expect(vm.status).toEqual('failed'); + }); + + it('returns default when MR has no pipeline', () => { + expect(vm.status).toEqual('success'); + }); + + it('returns pending when pipeline is active', () => { + vm.mr.pipeline = {}; + vm.mr.isPipelineActive = true; + expect(vm.status).toEqual('pending'); + }); + + it('returns failed when pipeline is failed', () => { + vm.mr.pipeline = {}; + vm.mr.isPipelineFailed = true; + expect(vm.status).toEqual('failed'); + }); + }); + describe('mergeButtonClass', () => { const defaultClass = 'btn btn-sm btn-success accept-merge-request'; const failedClass = `${defaultClass} btn-danger`; const inActionClass = `${defaultClass} btn-info`; - it('should return default class', () => { + it('defaults to success class', () => { + expect(vm.mergeButtonClass).toEqual(defaultClass); + }); + + it('returns success class for success status', () => { vm.mr.pipeline = true; expect(vm.mergeButtonClass).toEqual(defaultClass); }); - it('should return failed class when MR has CI but also has an unknown status', () => { + it('returns info class for pending status', () => { + vm.mr.pipeline = {}; + vm.mr.isPipelineActive = true; + expect(vm.mergeButtonClass).toEqual(inActionClass); + }); + + it('returns failed class for failed status', () => { vm.mr.hasCI = true; expect(vm.mergeButtonClass).toEqual(failedClass); }); + }); - it('should return default class when MR has no pipeline', () => { - expect(vm.mergeButtonClass).toEqual(defaultClass); + describe('status icon', () => { + it('defaults to tick icon', () => { + expect(vm.iconClass).toEqual('success'); + }); + + it('shows tick for success status', () => { + vm.mr.pipeline = true; + expect(vm.iconClass).toEqual('success'); }); - it('should return in action class when pipeline is active', () => { + it('shows tick for pending status', () => { vm.mr.pipeline = {}; vm.mr.isPipelineActive = true; - expect(vm.mergeButtonClass).toEqual(inActionClass); + expect(vm.iconClass).toEqual('success'); }); - it('should return failed class when pipeline is failed', () => { - vm.mr.pipeline = {}; - vm.mr.isPipelineFailed = true; - expect(vm.mergeButtonClass).toEqual(failedClass); + it('shows x for failed status', () => { + vm.mr.hasCI = true; + expect(vm.iconClass).toEqual('failed'); + }); + + it('shows x for merge not allowed', () => { + vm.mr.hasCI = true; + expect(vm.iconClass).toEqual('failed'); }); }); @@ -163,105 +217,52 @@ describe('MRWidgetReadyToMerge', () => { describe('isMergeButtonDisabled', () => { it('should return false with initial data', () => { + vm.mr.isMergeAllowed = true; expect(vm.isMergeButtonDisabled).toBeFalsy(); }); it('should return true when there is no commit message', () => { + vm.mr.isMergeAllowed = true; vm.commitMessage = ''; expect(vm.isMergeButtonDisabled).toBeTruthy(); }); it('should return true if merge is not allowed', () => { + vm.mr.isMergeAllowed = false; vm.mr.onlyAllowMergeIfPipelineSucceeds = true; - vm.mr.isPipelineFailed = true; expect(vm.isMergeButtonDisabled).toBeTruthy(); }); - it('should return true when there vm instance is making request', () => { + it('should return true when the vm instance is making request', () => { + vm.mr.isMergeAllowed = true; vm.isMakingRequest = true; expect(vm.isMergeButtonDisabled).toBeTruthy(); }); }); - - describe('Remove source branch checkbox', () => { - describe('when user can merge but cannot delete branch', () => { - it('isRemoveSourceBranchButtonDisabled should be true', () => { - expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true); - }); - - it('should be disabled in the rendered output', () => { - const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); - expect(checkboxElement.getAttribute('disabled')).toBe('disabled'); - }); - }); - - describe('when user can merge and can delete branch', () => { - beforeEach(() => { - this.customVm = createComponent({ - mr: { canRemoveSourceBranch: true }, - }); - }); - - it('isRemoveSourceBranchButtonDisabled should be false', () => { - expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false); - }); - - it('should be enabled in rendered output', () => { - const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input'); - expect(checkboxElement.getAttribute('disabled')).toBeNull(); - }); - }); - }); }); describe('methods', () => { - describe('isMergeAllowed', () => { - it('should return true when no pipeline and not required to succeed', () => { - vm.mr.onlyAllowMergeIfPipelineSucceeds = false; - vm.mr.isPipelinePassing = false; - expect(vm.isMergeAllowed()).toBeTruthy(); - }); - - it('should return true when pipeline failed and not required to succeed', () => { - vm.mr.onlyAllowMergeIfPipelineSucceeds = false; - vm.mr.isPipelinePassing = false; - expect(vm.isMergeAllowed()).toBeTruthy(); - }); - - it('should return false when pipeline failed and required to succeed', () => { - vm.mr.onlyAllowMergeIfPipelineSucceeds = true; - vm.mr.isPipelinePassing = false; - expect(vm.isMergeAllowed()).toBeFalsy(); - }); - - it('should return true when pipeline succeeded and required to succeed', () => { - vm.mr.onlyAllowMergeIfPipelineSucceeds = true; - vm.mr.isPipelinePassing = true; - expect(vm.isMergeAllowed()).toBeTruthy(); - }); - }); - describe('shouldShowMergeControls', () => { it('should return false when an external pipeline is running and required to succeed', () => { - spyOn(vm, 'isMergeAllowed').and.returnValue(false); + vm.mr.isMergeAllowed = false; vm.mr.isPipelineActive = false; expect(vm.shouldShowMergeControls()).toBeFalsy(); }); it('should return true when the build succeeded or build not required to succeed', () => { - spyOn(vm, 'isMergeAllowed').and.returnValue(true); + vm.mr.isMergeAllowed = true; vm.mr.isPipelineActive = false; expect(vm.shouldShowMergeControls()).toBeTruthy(); }); it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => { - spyOn(vm, 'isMergeAllowed').and.returnValue(false); + vm.mr.isMergeAllowed = false; vm.mr.isPipelineActive = true; expect(vm.shouldShowMergeControls()).toBeTruthy(); }); it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => { - spyOn(vm, 'isMergeAllowed').and.returnValue(true); + vm.mr.isMergeAllowed = true; vm.mr.isPipelineActive = true; expect(vm.shouldShowMergeControls()).toBeTruthy(); }); @@ -467,4 +468,96 @@ describe('MRWidgetReadyToMerge', () => { }); }); }); + + describe('Remove source branch checkbox', () => { + describe('when user can merge but cannot delete branch', () => { + it('isRemoveSourceBranchButtonDisabled should be true', () => { + expect(vm.isRemoveSourceBranchButtonDisabled).toBe(true); + }); + + it('should be disabled in the rendered output', () => { + const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); + expect(checkboxElement.getAttribute('disabled')).toBe('disabled'); + }); + }); + + describe('when user can merge and can delete branch', () => { + beforeEach(() => { + this.customVm = createComponent({ + mr: { canRemoveSourceBranch: true }, + }); + }); + + it('isRemoveSourceBranchButtonDisabled should be false', () => { + expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false); + }); + + it('should be enabled in rendered output', () => { + const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input'); + expect(checkboxElement.getAttribute('disabled')).toBeNull(); + }); + }); + }); + + describe('Merge controls', () => { + describe('when allowed to merge', () => { + beforeEach(() => { + vm = createComponent({ + mr: { isMergeAllowed: true }, + }); + }); + + it('shows remove source branch checkbox', () => { + expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeDefined(); + }); + + it('shows modify commit message button', () => { + expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); + }); + + it('does not show message about needing to resolve items', () => { + expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeNull(); + }); + }); + + describe('when not allowed to merge', () => { + beforeEach(() => { + vm = createComponent({ + mr: { isMergeAllowed: false }, + }); + }); + + it('does not show remove source branch checkbox', () => { + expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeNull(); + }); + + it('does not show modify commit message button', () => { + expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeNull(); + }); + + it('shows message to resolve all items before being allowed to merge', () => { + expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeDefined(); + }); + }); + }); + + describe('Commit message area', () => { + it('when using merge commits, should show "Modify commit message" button', () => { + const customVm = createComponent({ + mr: { ffOnlyEnabled: false }, + }); + + expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeNull(); + expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); + }); + + it('when fast-forward merge is enabled, only show fast-forward message', () => { + const customVm = createComponent({ + mr: { ffOnlyEnabled: true }, + }); + + expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeDefined(); + expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeNull(); + }); + }); }); diff --git a/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js b/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js index b63633c03b8..e667b4b3677 100644 --- a/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js +++ b/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js @@ -31,6 +31,7 @@ describe('MRWidgetService', () => { }); it('should have methods defined', () => { + window.history.pushState({}, null, '/'); const service = new MRWidgetService(mr); expect(service.merge()).toBeDefined(); diff --git a/spec/javascripts/vue_shared/ci_action_icons_spec.js b/spec/javascripts/vue_shared/ci_action_icons_spec.js deleted file mode 100644 index 3d53a5ab24d..00000000000 --- a/spec/javascripts/vue_shared/ci_action_icons_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import getActionIcon from '~/vue_shared/ci_action_icons'; -import cancelSVG from 'icons/_icon_action_cancel.svg'; -import retrySVG from 'icons/_icon_action_retry.svg'; -import playSVG from 'icons/_icon_action_play.svg'; -import stopSVG from 'icons/_icon_action_stop.svg'; - -describe('getActionIcon', () => { - it('should return an empty string', () => { - expect(getActionIcon()).toEqual(''); - }); - - it('should return cancel svg', () => { - expect(getActionIcon('icon_action_cancel')).toEqual(cancelSVG); - }); - - it('should return retry svg', () => { - expect(getActionIcon('icon_action_retry')).toEqual(retrySVG); - }); - - it('should return play svg', () => { - expect(getActionIcon('icon_action_play')).toEqual(playSVG); - }); - - it('should render stop svg', () => { - expect(getActionIcon('icon_action_stop')).toEqual(stopSVG); - }); -}); diff --git a/spec/javascripts/vue_shared/ci_status_icon_spec.js b/spec/javascripts/vue_shared/ci_status_icon_spec.js deleted file mode 100644 index b6621d6054d..00000000000 --- a/spec/javascripts/vue_shared/ci_status_icon_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { borderlessStatusIconEntityMap, statusIconEntityMap } from '~/vue_shared/ci_status_icons'; - -describe('CI status icons', () => { - const statuses = [ - 'icon_status_canceled', - 'icon_status_created', - 'icon_status_failed', - 'icon_status_manual', - 'icon_status_pending', - 'icon_status_running', - 'icon_status_skipped', - 'icon_status_success', - 'icon_status_warning', - ]; - - it('should have a dictionary for borderless icons', () => { - statuses.forEach((status) => { - expect(borderlessStatusIconEntityMap[status]).toBeDefined(); - }); - }); - - it('should have a dictionary for icons', () => { - statuses.forEach((status) => { - expect(statusIconEntityMap[status]).toBeDefined(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js index daed4da3e15..8762ce9903b 100644 --- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js +++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js @@ -1,84 +1,88 @@ import Vue from 'vue'; import ciBadge from '~/vue_shared/components/ci_badge_link.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('CI Badge Link Component', () => { let CIBadge; + let vm; const statuses = { canceled: { text: 'canceled', label: 'canceled', group: 'canceled', - icon: 'icon_status_canceled', + icon: 'status_canceled', details_path: 'status/canceled', }, created: { text: 'created', label: 'created', group: 'created', - icon: 'icon_status_created', + icon: 'status_created', details_path: 'status/created', }, failed: { text: 'failed', label: 'failed', group: 'failed', - icon: 'icon_status_failed', + icon: 'status_failed', details_path: 'status/failed', }, manual: { text: 'manual', label: 'manual action', group: 'manual', - icon: 'icon_status_manual', + icon: 'status_manual', details_path: 'status/manual', }, pending: { text: 'pending', label: 'pending', group: 'pending', - icon: 'icon_status_pending', + icon: 'status_pending', details_path: 'status/pending', }, running: { text: 'running', label: 'running', group: 'running', - icon: 'icon_status_running', + icon: 'status_running', details_path: 'status/running', }, skipped: { text: 'skipped', label: 'skipped', group: 'skipped', - icon: 'icon_status_skipped', + icon: 'status_skipped', details_path: 'status/skipped', }, success_warining: { text: 'passed', label: 'passed', group: 'success_with_warnings', - icon: 'icon_status_warning', + icon: 'status_warning', details_path: 'status/warning', }, success: { text: 'passed', label: 'passed', group: 'passed', - icon: 'icon_status_success', + icon: 'status_success', details_path: 'status/passed', }, }; - it('should render each status badge', () => { + beforeEach(() => { CIBadge = Vue.extend(ciBadge); - Object.keys(statuses).map((status) => { - const vm = new CIBadge({ - propsData: { - status: statuses[status], - }, - }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + it('should render each status badge', () => { + Object.keys(statuses).map((status) => { + vm = mountComponent(CIBadge, { status: statuses[status] }); expect(vm.$el.getAttribute('href')).toEqual(statuses[status].details_path); expect(vm.$el.textContent.trim()).toEqual(statuses[status].text); expect(vm.$el.getAttribute('class')).toEqual(`ci-status ci-${statuses[status].group}`); @@ -86,4 +90,9 @@ describe('CI Badge Link Component', () => { return vm; }); }); + + it('should not render label', () => { + vm = mountComponent(CIBadge, { status: statuses.canceled, showText: false }); + expect(vm.$el.textContent.trim()).toEqual(''); + }); }); diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js new file mode 100644 index 00000000000..104da4473ce --- /dev/null +++ b/spec/javascripts/vue_shared/components/icon_spec.js @@ -0,0 +1,48 @@ +import Vue from 'vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Sprite Icon Component', function () { + describe('Initialization', function () { + let icon; + + beforeEach(function () { + const IconComponent = Vue.extend(Icon); + + icon = mountComponent(IconComponent, { + name: 'test', + size: 99, + cssClasses: 'extraclasses', + }); + }); + + afterEach(() => { + icon.$destroy(); + }); + + it('should return a defined Vue component', function () { + expect(icon).toBeDefined(); + }); + + it('should have <svg> as a child element', function () { + expect(icon.$el.tagName).toBe('svg'); + }); + + it('should have <use> as a child element with the correct href', function () { + expect(icon.$el.firstChild.tagName).toBe('use'); + expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#test`); + }); + + it('should properly compute iconSizeClass', function () { + expect(icon.iconSizeClass).toBe('s99'); + }); + + it('should properly render img css', function () { + const classList = icon.$el.classList; + const containsSizeClass = classList.contains('s99'); + const containsCustomClass = classList.contains('extraclasses'); + expect(containsSizeClass).toBe(true); + expect(containsCustomClass).toBe(true); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/issue/confidential_issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/confidential_issue_warning_spec.js deleted file mode 100644 index 6df08f3ebe7..00000000000 --- a/spec/javascripts/vue_shared/components/issue/confidential_issue_warning_spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import Vue from 'vue'; -import confidentialIssue from '~/vue_shared/components/issue/confidential_issue_warning.vue'; - -describe('Confidential Issue Warning Component', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(confidentialIssue); - vm = new Component().$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render confidential issue warning information', () => { - expect(vm.$el.querySelector('i').className).toEqual('fa fa-eye-slash'); - expect(vm.$el.querySelector('span').textContent.trim()).toEqual('This is a confidential issue. Your comment will not be visible to the public.'); - }); -}); diff --git a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js new file mode 100644 index 00000000000..2cf4d8e00ed --- /dev/null +++ b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js @@ -0,0 +1,46 @@ +import Vue from 'vue'; +import issueWarning from '~/vue_shared/components/issue/issue_warning.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +const IssueWarning = Vue.extend(issueWarning); + +function formatWarning(string) { + // Replace newlines with a space then replace multiple spaces with one space + return string.trim().replace(/\n/g, ' ').replace(/\s\s+/g, ' '); +} + +describe('Issue Warning Component', () => { + describe('isLocked', () => { + it('should render locked issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isLocked: true, + }); + + expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-lock'); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is locked. Only project members can comment.'); + }); + }); + + describe('isConfidential', () => { + it('should render confidential issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isConfidential: true, + }); + + expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-eye-slash'); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This is a confidential issue. Your comment will not be visible to the public.'); + }); + }); + + describe('isLocked and isConfidential', () => { + it('should render locked and confidential issue warning information', () => { + const vm = mountComponent(IssueWarning, { + isLocked: true, + isConfidential: true, + }); + + expect(vm.$el.querySelector('i')).toBeFalsy(); + expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is confidential and locked. People without permission will never get a notification and won\'t be able to comment.'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/loading_button_spec.js b/spec/javascripts/vue_shared/components/loading_button_spec.js new file mode 100644 index 00000000000..97c8a08fcdd --- /dev/null +++ b/spec/javascripts/vue_shared/components/loading_button_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import loadingButton from '~/vue_shared/components/loading_button.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const LABEL = 'Hello'; + +describe('LoadingButton', function () { + let vm; + let LoadingButton; + + beforeEach(() => { + LoadingButton = Vue.extend(loadingButton); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('loading spinner', () => { + it('shown when loading', () => { + vm = mountComponent(LoadingButton, { + loading: true, + }); + + expect(vm.$el.querySelector('.js-loading-button-icon')).toBeDefined(); + }); + }); + + describe('disabled state', () => { + it('disabled when loading', () => { + vm = mountComponent(LoadingButton, { + loading: true, + }); + + expect(vm.$el.disabled).toEqual(true); + }); + + it('not disabled when normal', () => { + vm = mountComponent(LoadingButton, { + loading: false, + }); + + expect(vm.$el.disabled).toEqual(false); + }); + }); + + describe('label', () => { + it('shown when normal', () => { + vm = mountComponent(LoadingButton, { + loading: false, + label: LABEL, + }); + const label = vm.$el.querySelector('.js-loading-button-label'); + + expect(label.textContent.trim()).toEqual(LABEL); + }); + + it('shown when loading', () => { + vm = mountComponent(LoadingButton, { + loading: true, + label: LABEL, + }); + const label = vm.$el.querySelector('.js-loading-button-label'); + + expect(label.textContent.trim()).toEqual(LABEL); + }); + }); + + describe('click callback prop', () => { + it('calls given callback when normal', () => { + vm = mountComponent(LoadingButton, { + loading: false, + }); + spyOn(vm, '$emit'); + + vm.$el.click(); + + expect(vm.$emit).toHaveBeenCalledWith('click', jasmine.any(Object)); + }); + + it('does not call given callback when disabled because of loading', () => { + vm = mountComponent(LoadingButton, { + loading: true, + indeterminate: true, + }); + spyOn(vm, '$emit'); + + vm.$el.click(); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index 60a5c2ae74e..65c49b9f30b 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -42,12 +42,14 @@ describe('Markdown field component', () => { beforeEach(() => { spyOn(Vue.http, 'post').and.callFake(() => new Promise((resolve) => { - resolve({ - json() { - return { - body: '<p>markdown preview</p>', - }; - }, + setTimeout(() => { + resolve({ + json() { + return { + body: '<p>markdown preview</p>', + }; + }, + }); }); })); diff --git a/spec/javascripts/notes/components/issue_placeholder_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js index 6e5275087f3..ba8ab0b2cd7 100644 --- a/spec/javascripts/notes/components/issue_placeholder_note_spec.js +++ b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; -import issuePlaceholderNote from '~/notes/components/issue_placeholder_note.vue'; +import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; import store from '~/notes/stores'; -import { userDataMock } from '../mock_data'; +import { userDataMock } from '../../../notes/mock_data'; describe('issue placeholder system note component', () => { let vm; diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js new file mode 100644 index 00000000000..7b8e6c330c2 --- /dev/null +++ b/spec/javascripts/vue_shared/components/notes/placeholder_system_note_spec.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import placeholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +describe('placeholder system note component', () => { + let PlaceholderSystemNote; + let vm; + + beforeEach(() => { + PlaceholderSystemNote = Vue.extend(placeholderSystemNote); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render system note placeholder with plain text', () => { + vm = mountComponent(PlaceholderSystemNote, { + note: { body: 'This is a placeholder' }, + }); + + expect(vm.$el.tagName).toEqual('LI'); + expect(vm.$el.querySelector('.timeline-content em').textContent.trim()).toEqual('This is a placeholder'); + }); +}); diff --git a/spec/javascripts/notes/components/issue_system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js index c317ce32716..36aaf0a6c2e 100644 --- a/spec/javascripts/notes/components/issue_system_note_spec.js +++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import issueSystemNote from '~/notes/components/issue_system_note.vue'; +import issueSystemNote from '~/vue_shared/components/notes/system_note.vue'; import store from '~/notes/stores'; describe('issue system note', () => { @@ -33,6 +33,10 @@ describe('issue system note', () => { }).$mount(); }); + afterEach(() => { + vm.$destroy(); + }); + it('should render a list item with correct id', () => { expect(vm.$el.getAttribute('id')).toEqual(`note_${props.note.id}`); }); diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js new file mode 100644 index 00000000000..aa93134f2dd --- /dev/null +++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js @@ -0,0 +1,84 @@ +import Vue from 'vue'; +import { placeholderImage } from '~/lazy_loader'; +import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import mountComponent from '../../../helpers/vue_mount_component_helper'; + +const DEFAULT_PROPS = { + size: 99, + imgSrc: 'myavatarurl.com', + imgAlt: 'mydisplayname', + cssClasses: 'myextraavatarclass', + tooltipText: 'tooltip text', + tooltipPlacement: 'bottom', +}; + +describe('User Avatar Image Component', function () { + let vm; + let UserAvatarImage; + + beforeEach(() => { + UserAvatarImage = Vue.extend(userAvatarImage); + }); + + describe('Initialization', function () { + beforeEach(function () { + vm = mountComponent(UserAvatarImage, { + ...DEFAULT_PROPS, + }).$mount(); + }); + + it('should return a defined Vue component', function () { + expect(vm).toBeDefined(); + }); + + it('should have <img> as a child element', function () { + expect(vm.$el.tagName).toBe('IMG'); + expect(vm.$el.getAttribute('src')).toBe(DEFAULT_PROPS.imgSrc); + expect(vm.$el.getAttribute('data-src')).toBe(DEFAULT_PROPS.imgSrc); + expect(vm.$el.getAttribute('alt')).toBe(DEFAULT_PROPS.imgAlt); + }); + + it('should properly compute tooltipContainer', function () { + expect(vm.tooltipContainer).toBe('body'); + }); + + it('should properly render tooltipContainer', function () { + expect(vm.$el.getAttribute('data-container')).toBe('body'); + }); + + it('should properly compute avatarSizeClass', function () { + expect(vm.avatarSizeClass).toBe('s99'); + }); + + it('should properly render img css', function () { + const classList = vm.$el.classList; + const containsAvatar = classList.contains('avatar'); + const containsSizeClass = classList.contains('s99'); + const containsCustomClass = classList.contains(DEFAULT_PROPS.cssClasses); + const lazyClass = classList.contains('lazy'); + + expect(containsAvatar).toBe(true); + expect(containsSizeClass).toBe(true); + expect(containsCustomClass).toBe(true); + expect(lazyClass).toBe(false); + }); + }); + + describe('Initialization when lazy', function () { + beforeEach(function () { + vm = mountComponent(UserAvatarImage, { + ...DEFAULT_PROPS, + lazy: true, + }).$mount(); + }); + + it('should add lazy attributes', function () { + const classList = vm.$el.classList; + const lazyClass = classList.contains('lazy'); + + expect(lazyClass).toBe(true); + expect(vm.$el.getAttribute('src')).toBe(placeholderImage); + expect(vm.$el.getAttribute('data-src')).toBe(DEFAULT_PROPS.imgSrc); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js index 52e450e9ba5..8450ad9dbcb 100644 --- a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js +++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js @@ -11,6 +11,7 @@ describe('User Avatar Link Component', function () { imgCssClasses: 'myextraavatarclass', tooltipText: 'tooltip text', tooltipPlacement: 'bottom', + username: 'username', }; const UserAvatarLinkComponent = Vue.extend(UserAvatarLink); @@ -47,4 +48,42 @@ describe('User Avatar Link Component', function () { expect(this.userAvatarLink[key]).toBeDefined(); }); }); + + describe('no username', function () { + beforeEach(function (done) { + this.userAvatarLink.username = ''; + + Vue.nextTick(done); + }); + + it('should only render image tag in link', function () { + const childElements = this.userAvatarLink.$el.childNodes; + expect(childElements[0].tagName).toBe('IMG'); + + // Vue will render the hidden component as <!----> + expect(childElements[1].tagName).toBeUndefined(); + }); + + it('should render avatar image tooltip', function () { + expect(this.userAvatarLink.$el.querySelector('img').dataset.originalTitle).toEqual(this.propsData.tooltipText); + }); + }); + + describe('username', function () { + it('should not render avatar image tooltip', function () { + expect(this.userAvatarLink.$el.querySelector('img').dataset.originalTitle).toEqual(''); + }); + + it('should render username prop in <span>', function () { + expect(this.userAvatarLink.$el.querySelector('span').innerText.trim()).toEqual(this.propsData.username); + }); + + it('should render text tooltip for <span>', function () { + expect(this.userAvatarLink.$el.querySelector('span').dataset.originalTitle).toEqual(this.propsData.tooltipText); + }); + + it('should render text tooltip placement for <span>', function () { + expect(this.userAvatarLink.$el.querySelector('span').getAttribute('tooltip-placement')).toEqual(this.propsData.tooltipPlacement); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js index b8d639ffbec..b8d639ffbec 100644 --- a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js +++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js diff --git a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js deleted file mode 100644 index 8daa7610274..00000000000 --- a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import Vue from 'vue'; -import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; - -const UserAvatarImageComponent = Vue.extend(UserAvatarImage); - -describe('User Avatar Image Component', function () { - describe('Initialization', function () { - beforeEach(function () { - this.propsData = { - size: 99, - imgSrc: 'myavatarurl.com', - imgAlt: 'mydisplayname', - cssClasses: 'myextraavatarclass', - tooltipText: 'tooltip text', - tooltipPlacement: 'bottom', - }; - - this.userAvatarImage = new UserAvatarImageComponent({ - propsData: this.propsData, - }).$mount(); - }); - - it('should return a defined Vue component', function () { - expect(this.userAvatarImage).toBeDefined(); - }); - - it('should have <img> as a child element', function () { - expect(this.userAvatarImage.$el.tagName).toBe('IMG'); - }); - - it('should properly compute tooltipContainer', function () { - expect(this.userAvatarImage.tooltipContainer).toBe('body'); - }); - - it('should properly render tooltipContainer', function () { - expect(this.userAvatarImage.$el.getAttribute('data-container')).toBe('body'); - }); - - it('should properly compute avatarSizeClass', function () { - expect(this.userAvatarImage.avatarSizeClass).toBe('s99'); - }); - - it('should properly render img css', function () { - const classList = this.userAvatarImage.$el.classList; - const containsAvatar = classList.contains('avatar'); - const containsSizeClass = classList.contains('s99'); - const containsCustomClass = classList.contains('myextraavatarclass'); - - expect(containsAvatar).toBe(true); - expect(containsSizeClass).toBe(true); - expect(containsCustomClass).toBe(true); - }); - }); -}); diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index bd18f79cea7..7047053d131 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -1,7 +1,6 @@ /* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */ -/* global Dropzone */ /* global Mousetrap */ - +import Dropzone from 'dropzone'; import ZenMode from '~/zen_mode'; (function() { diff --git a/spec/lib/additional_email_headers_interceptor_spec.rb b/spec/lib/additional_email_headers_interceptor_spec.rb index 580450eef1e..b5c1a360ba9 100644 --- a/spec/lib/additional_email_headers_interceptor_spec.rb +++ b/spec/lib/additional_email_headers_interceptor_spec.rb @@ -1,12 +1,29 @@ require 'spec_helper' describe AdditionalEmailHeadersInterceptor do - it 'adds Auto-Submitted header' do - mail = ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello').deliver + let(:mail) do + ActionMailer::Base.mail(to: 'test@mail.com', from: 'info@mail.com', body: 'hello') + end + + before do + mail.deliver_now + end + it 'adds Auto-Submitted header' do expect(mail.header['To'].value).to eq('test@mail.com') expect(mail.header['From'].value).to eq('info@mail.com') expect(mail.header['Auto-Submitted'].value).to eq('auto-generated') expect(mail.header['X-Auto-Response-Suppress'].value).to eq('All') end + + context 'when the same mail object is sent twice' do + before do + mail.deliver_now + end + + it 'does not add the Auto-Submitted header twice' do + expect(mail.header['Auto-Submitted'].value).to eq('auto-generated') + expect(mail.header['X-Auto-Response-Suppress'].value).to eq('All') + end + end end diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb index 049d025a5b9..84adaebdcbe 100644 --- a/spec/lib/banzai/commit_renderer_spec.rb +++ b/spec/lib/banzai/commit_renderer_spec.rb @@ -10,7 +10,7 @@ describe Banzai::CommitRenderer do described_class::ATTRIBUTES.each do |attr| expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr) + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, {}) end described_class.render([project.commit], project, user) diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb new file mode 100644 index 00000000000..a3ad056efcd --- /dev/null +++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Banzai::Filter::AbsoluteLinkFilter do + def filter(doc, context = {}) + described_class.call(doc, context) + end + + context 'with html links' do + context 'if only_path is false' do + let(:only_path_context) do + { only_path: false } + end + let(:fake_url) { 'http://www.example.com' } + + before do + allow(Gitlab.config.gitlab).to receive(:url).and_return(fake_url) + end + + context 'has the .gfm class' do + it 'converts a relative url into absolute' do + doc = filter(link('/foo', 'gfm'), only_path_context) + expect(doc.at_css('a')['href']).to eq "#{fake_url}/foo" + end + + it 'does not change the url if it already absolute' do + doc = filter(link("#{fake_url}/foo", 'gfm'), only_path_context) + expect(doc.at_css('a')['href']).to eq "#{fake_url}/foo" + end + + context 'if relative_url_root is set' do + it 'joins the url without without doubling the path' do + allow(Gitlab.config.gitlab).to receive(:url).and_return("#{fake_url}/gitlab/") + doc = filter(link("/gitlab/foo", 'gfm'), only_path_context) + expect(doc.at_css('a')['href']).to eq "#{fake_url}/gitlab/foo" + end + end + end + + context 'has not the .gfm class' do + it 'does not convert a relative url into absolute' do + doc = filter(link('/foo'), only_path_context) + expect(doc.at_css('a')['href']).to eq '/foo' + end + end + end + + context 'if only_path is not false' do + it 'does not convert a relative url into absolute' do + expect(filter(link('/foo', 'gfm')).at_css('a')['href']).to eq '/foo' + expect(filter(link('/foo')).at_css('a')['href']).to eq '/foo' + end + end + end + + def link(path, css_class = '') + %(<a class="#{css_class}" href="#{path}">example</a>) + end +end diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index 97d612e6347..ca76d6f0881 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -15,9 +15,13 @@ describe Banzai::Filter::GollumTagsFilter do context 'linking internal images' do it 'creates img tag if image exists' do - file = Gollum::File.new(project_wiki.wiki) - expect(file).to receive(:path).and_return('images/image.jpg') - expect(project_wiki).to receive(:find_file).with('images/image.jpg').and_return(file) + gollum_file_double = double('Gollum::File', + mime_type: 'image/jpeg', + name: 'images/image.jpg', + path: 'images/image.jpg', + raw_data: '') + wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) + expect(project_wiki).to receive(:find_file).with('images/image.jpg').and_return(wiki_file) tag = '[[images/image.jpg]]' doc = filter("See #{tag}", project_wiki: project_wiki) diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 9c74c9b8c99..3c98b18f99b 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -317,6 +317,68 @@ describe Banzai::Filter::IssueReferenceFilter do end end + context 'group context' do + let(:group) { create(:group) } + let(:context) { { project: nil, group: group } } + + it 'ignores shorthanded issue reference' do + reference = "##{issue.iid}" + text = "Fixed #{reference}" + + expect(reference_filter(text, context).to_html).to eq(text) + end + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(described_class).to receive(:find_object) + .with(project, issue.iid) + .and_return(nil) + + reference = "#{project.full_path}##{issue.iid}" + text = "Issue #{reference}" + + expect(reference_filter(text, context).to_html).to eq(text) + end + + it 'links to a valid reference for complete cross-reference' do + reference = "#{project.full_path}##{issue.iid}" + doc = reference_filter("See #{reference}", context) + + expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project) + end + + it 'ignores reference for shorthand cross-reference' do + reference = "#{project.path}##{issue.iid}" + text = "See #{reference}" + + expect(reference_filter(text, context).to_html).to eq(text) + end + + it 'links to a valid reference for url cross-reference' do + reference = helper.url_for_issue(issue.iid, project) + "#note_123" + + doc = reference_filter("See #{reference}", context) + + expect(doc.css('a').first.attr('href')).to eq(helper.url_for_issue(issue.iid, project) + "#note_123") + end + + it 'links to a valid reference for cross-reference in link href' do + reference = "#{helper.url_for_issue(issue.iid, project) + "#note_123"}" + reference_link = %{<a href="#{reference}">Reference</a>} + + doc = reference_filter("See #{reference_link}", context) + + expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project) + "#note_123" + end + + it 'links to a valid reference for issue reference in the link href' do + reference = issue.to_reference(group) + reference_link = %{<a href="#{reference}">Reference</a>} + doc = reference_filter("See #{reference_link}", context) + + expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project) + end + end + describe '#issues_per_project' do context 'using an internal issue tracker' do it 'returns a Hash containing the issues per project' do diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 2cd30a5e302..862b1fe3fd3 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -594,4 +594,16 @@ describe Banzai::Filter::LabelReferenceFilter do expect(reference_filter(act).to_html).to eq exp end end + + describe 'group context' do + it 'points to referenced project issues page' do + project = create(:project) + label = create(:label, project: project) + reference = "#{project.full_path}~#{label.name}" + + result = reference_filter("See #{reference}", { project: nil, group: create(:group) } ) + + expect(result.css('a').first.attr('href')).to eq(urls.project_issues_url(project, label_name: label.name)) + end + end end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index ed2788f8a33..158844e25ae 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -214,4 +214,14 @@ describe Banzai::Filter::MergeRequestReferenceFilter do expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) end end + + context 'group context' do + it 'links to a valid reference' do + reference = "#{project.full_path}!#{merge.iid}" + + result = reference_filter("See #{reference}", { project: nil, group: create(:group) } ) + + expect(result.css('a').first.attr('href')).to eq(urls.project_merge_request_url(project, merge)) + end + end end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index fe7a8c84c9e..84578668133 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -343,4 +343,15 @@ describe Banzai::Filter::MilestoneReferenceFilter do expect(doc.css('a')).to be_empty end end + + context 'group context' do + it 'links to a valid reference' do + milestone = create(:milestone, project: project) + reference = "#{project.full_path}%#{milestone.iid}" + + result = reference_filter("See #{reference}", { project: nil, group: create(:group) } ) + + expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone)) + end + end end diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 5f41e28fece..17a620ef603 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -217,6 +217,11 @@ describe Banzai::Filter::SanitizationFilter do output: '<img>' }, + 'protocol-based JS injection: Unicode' => { + input: %Q(<a href="\u0001java\u0003script:alert('XSS')">foo</a>), + output: '<a>foo</a>' + }, + 'protocol-based JS injection: spaces and entities' => { input: '<a href="  javascript:alert(\'XSS\');">foo</a>', output: '<a href="">foo</a>' diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index 90ac4c7b238..3a07a6dc179 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -201,4 +201,14 @@ describe Banzai::Filter::SnippetReferenceFilter do expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/) end end + + context 'group context' do + it 'links to a valid reference' do + reference = "#{project.full_path}$#{snippet.id}" + + result = reference_filter("See #{reference}", { project: nil, group: create(:group) } ) + + expect(result.css('a').first.attr('href')).to eq(urls.project_snippet_url(project, snippet)) + end + end end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 34dac1db69a..fc03741976e 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -208,6 +208,39 @@ describe Banzai::Filter::UserReferenceFilter do end end + context 'in group context' do + let(:group) { create(:group) } + let(:group_member) { create(:user) } + + before do + group.add_developer(group_member) + end + + let(:context) { { author: group_member, project: nil, group: group } } + + it 'supports a special @all mention' do + reference = User.reference_prefix + 'all' + doc = reference_filter("Hey #{reference}", context) + + expect(doc.css('a').length).to eq(1) + expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) + end + + it 'supports mentioning a single user' do + reference = group_member.to_reference + doc = reference_filter("Hey #{reference}", context) + + expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member) + end + + it 'supports mentioning a group' do + reference = group.to_reference + doc = reference_filter("Hey #{reference}", context) + + expect(doc.css('a').first.attr('href')).to eq urls.user_url(group) + end + end + describe '#namespaces' do it 'returns a Hash containing all Namespaces' do document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb deleted file mode 100644 index 32764bee5eb..00000000000 --- a/spec/lib/banzai/note_renderer_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe Banzai::NoteRenderer do - describe '.render' do - it 'renders a Note' do - note = double(:note) - project = double(:project) - wiki = double(:wiki) - user = double(:user) - - expect(Banzai::ObjectRenderer).to receive(:new) - .with(project, user, - requested_path: 'foo', - project_wiki: wiki, - ref: 'bar') - .and_call_original - - expect_any_instance_of(Banzai::ObjectRenderer) - .to receive(:render).with([note], :note) - - described_class.render([note], project, user, 'foo', wiki, 'bar') - end - end -end diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index b172a1b718c..074d521a5c6 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -22,7 +22,7 @@ describe Banzai::ObjectRenderer do end it 'retrieves field content using Banzai::Renderer.render_field' do - expect(Banzai::Renderer).to receive(:render_field).with(object, :note).and_call_original + expect(Banzai::Renderer).to receive(:render_field).with(object, :note, {}).and_call_original renderer.render([object], :note) end @@ -68,7 +68,7 @@ describe Banzai::ObjectRenderer do end it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do - expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title).and_call_original + expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title, {}).and_call_original renderer.render([commit], :title) end diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb index da42272bbef..650cecfc778 100644 --- a/spec/lib/banzai/renderer_spec.rb +++ b/spec/lib/banzai/renderer_spec.rb @@ -18,7 +18,7 @@ describe Banzai::Renderer do let(:commit) { create(:project, :repository).commit } it 'returns cacheless render field' do - expect(renderer).to receive(:cacheless_render_field).with(commit, :title) + expect(renderer).to receive(:cacheless_render_field).with(commit, :title, {}) renderer.render_field(commit, :title) end @@ -31,7 +31,14 @@ describe Banzai::Renderer do let(:object) { fake_object(fresh: false) } it 'caches and returns the result' do - expect(object).to receive(:refresh_markdown_cache!).with(do_update: true) + expect(object).to receive(:refresh_markdown_cache!) + + is_expected.to eq('field_html') + end + + it "skips database caching on a GitLab read-only instance" do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + expect(object).to receive(:refresh_markdown_cache!) is_expected.to eq('field_html') end diff --git a/spec/lib/github/client_spec.rb b/spec/lib/github/client_spec.rb new file mode 100644 index 00000000000..b846096fe25 --- /dev/null +++ b/spec/lib/github/client_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Github::Client do + let(:connection) { spy } + let(:rate_limit) { double(get: [false, 1]) } + let(:client) { described_class.new({}) } + let(:results) { double } + let(:response) { double } + + before do + allow(Faraday).to receive(:new).and_return(connection) + allow(Github::RateLimit).to receive(:new).with(connection).and_return(rate_limit) + end + + describe '#get' do + before do + allow(Github::Response).to receive(:new).with(results).and_return(response) + end + + it 'uses a default per_page param' do + expect(connection).to receive(:get).with('/foo', per_page: 100).and_return(results) + + expect(client.get('/foo')).to eq(response) + end + + context 'with per_page given' do + it 'overwrites the default per_page' do + expect(connection).to receive(:get).with('/foo', per_page: 30).and_return(results) + + expect(client.get('/foo', per_page: 30)).to eq(response) + end + end + end +end diff --git a/spec/lib/github/import/legacy_diff_note_spec.rb b/spec/lib/github/import/legacy_diff_note_spec.rb new file mode 100644 index 00000000000..8c50b46cacb --- /dev/null +++ b/spec/lib/github/import/legacy_diff_note_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe Github::Import::LegacyDiffNote do + describe '#type' do + it 'returns the original note type' do + expect(described_class.new.type).to eq('LegacyDiffNote') + end + end +end diff --git a/spec/lib/github/import/note_spec.rb b/spec/lib/github/import/note_spec.rb new file mode 100644 index 00000000000..fcdccd9e097 --- /dev/null +++ b/spec/lib/github/import/note_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe Github::Import::Note do + describe '#type' do + it 'returns the original note type' do + expect(described_class.new.type).to eq('Note') + end + end +end diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb new file mode 100644 index 00000000000..c86d30ce6df --- /dev/null +++ b/spec/lib/gitlab/app_logger_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Gitlab::AppLogger, :request_store do + subject { described_class } + + it 'builds a logger once' do + expect(::Logger).to receive(:new).and_call_original + + subject.info('hello world') + subject.error('hello again') + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index af1db2c3455..54a853c9ce3 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Auth do describe 'constants' do it 'API_SCOPES contains all scopes for API access' do - expect(subject::API_SCOPES).to eq [:api, :read_user] + expect(subject::API_SCOPES).to eq %i[api read_user sudo] end it 'OPENID_SCOPES contains all scopes for OpenID Connect' do @@ -19,7 +19,7 @@ describe Gitlab::Auth do it 'optional_scopes contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.optional_scopes).to eq %i[read_user read_registry openid] + expect(subject.optional_scopes).to eq %i[read_user sudo read_registry openid] end context 'registry_scopes' do @@ -164,7 +164,7 @@ describe Gitlab::Auth do personal_access_token = create(:personal_access_token, scopes: ['api']) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') - expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities)) + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, full_authentication_abilities)) end context 'when registry is enabled' do @@ -176,7 +176,7 @@ describe Gitlab::Auth do personal_access_token = create(:personal_access_token, scopes: ['read_registry']) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') - expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image])) + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, [:read_container_image])) end end @@ -184,14 +184,14 @@ describe Gitlab::Auth do impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api']) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') - expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities)) + expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_access_token, full_authentication_abilities)) end it 'limits abilities based on scope' do personal_access_token = create(:personal_access_token, scopes: ['read_user']) expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') - expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [])) + expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, [])) end it 'fails if password is nil' do @@ -234,7 +234,7 @@ describe Gitlab::Auth do it 'throws an error suggesting user create a PAT when internal auth is disabled' do allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } - expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError) + expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError) end end diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb new file mode 100644 index 00000000000..1a4ea2bac48 --- /dev/null +++ b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do + let(:migration) { described_class.new } + + let(:base1) { create(:project) } + let(:base1_fork1) { create(:project) } + let(:base1_fork2) { create(:project) } + + let(:base2) { create(:project) } + let(:base2_fork1) { create(:project) } + let(:base2_fork2) { create(:project) } + + let(:fork_of_fork) { create(:project) } + let(:fork_of_fork2) { create(:project) } + let(:second_level_fork) { create(:project) } + let(:third_level_fork) { create(:project) } + + let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } + let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } + + let!(:forked_project_links) { table(:forked_project_links) } + let!(:fork_networks) { table(:fork_networks) } + let!(:fork_network_members) { table(:fork_network_members) } + + before do + # The fork-network relation created for the forked project + fork_networks.create(id: 1, root_project_id: base1.id) + fork_network_members.create(project_id: base1.id, fork_network_id: 1) + fork_networks.create(id: 2, root_project_id: base2.id) + fork_network_members.create(project_id: base2.id, fork_network_id: 2) + + # Normal fork links + forked_project_links.create(id: 1, forked_from_project_id: base1.id, forked_to_project_id: base1_fork1.id) + forked_project_links.create(id: 2, forked_from_project_id: base1.id, forked_to_project_id: base1_fork2.id) + forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id) + forked_project_links.create(id: 4, forked_from_project_id: base2.id, forked_to_project_id: base2_fork2.id) + + # Fork links + forked_project_links.create(id: 5, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork.id) + forked_project_links.create(id: 6, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork2.id) + + # Forks 3 levels down + forked_project_links.create(id: 7, forked_from_project_id: fork_of_fork.id, forked_to_project_id: second_level_fork.id) + forked_project_links.create(id: 8, forked_from_project_id: second_level_fork.id, forked_to_project_id: third_level_fork.id) + + migration.perform(1, 8) + end + + it 'creates a memberships for the direct forks' do + base1_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1_fork1.id) + base1_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1_fork2.id) + base2_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2_fork1.id) + base2_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2_fork2.id) + + expect(base1_fork1_membership.forked_from_project_id).to eq(base1.id) + expect(base1_fork2_membership.forked_from_project_id).to eq(base1.id) + expect(base2_fork1_membership.forked_from_project_id).to eq(base2.id) + expect(base2_fork2_membership.forked_from_project_id).to eq(base2.id) + end + + it 'adds the fork network members for forks of forks' do + fork_of_fork_membership = fork_network_members.find_by(project_id: fork_of_fork.id, + fork_network_id: fork_network1.id) + fork_of_fork2_membership = fork_network_members.find_by(project_id: fork_of_fork2.id, + fork_network_id: fork_network1.id) + second_level_fork_membership = fork_network_members.find_by(project_id: second_level_fork.id, + fork_network_id: fork_network1.id) + third_level_fork_membership = fork_network_members.find_by(project_id: third_level_fork.id, + fork_network_id: fork_network1.id) + + expect(fork_of_fork_membership.forked_from_project_id).to eq(base1_fork1.id) + expect(fork_of_fork2_membership.forked_from_project_id).to eq(base1_fork1.id) + expect(second_level_fork_membership.forked_from_project_id).to eq(fork_of_fork.id) + expect(third_level_fork_membership.forked_from_project_id).to eq(second_level_fork.id) + end + + it 'reschedules itself when there are missing members' do + allow(migration).to receive(:missing_members?).and_return(true) + + expect(BackgroundMigrationWorker) + .to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [1, 3]) + + migration.perform(1, 3) + end + + it 'can be repeated without effect' do + expect { fork_network_members.count }.not_to change { migration.perform(1, 7) } + end + + it 'knows it is finished for this range' do + expect(migration.missing_members?(1, 7)).to be_falsy + end + + context 'with more forks' do + before do + forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) + forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id) + end + + it 'only processes a single batch of links at a time' do + expect(fork_network_members.count).to eq(10) + + migration.perform(8, 10) + + expect(fork_network_members.count).to eq(12) + end + + it 'knows when not all memberships withing a batch have been created' do + expect(migration.missing_members?(8, 10)).to be_truthy + end + end +end diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb new file mode 100644 index 00000000000..26d48cc8201 --- /dev/null +++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do + context 'when GpgKey exists' do + let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } + + before do + GpgKeySubkey.destroy_all + end + + it 'generate the subkeys' do + expect do + described_class.new.perform(gpg_key.id) + end.to change { gpg_key.subkeys.count }.from(0).to(2) + end + + it 'schedules the signature update worker' do + expect(InvalidGpgSignatureUpdateWorker).to receive(:perform_async).with(gpg_key.id) + + described_class.new.perform(gpg_key.id) + end + end + + context 'when GpgKey does not exist' do + it 'does not do anything' do + expect(Gitlab::Gpg).not_to receive(:subkeys_from_key) + expect(InvalidGpgSignatureUpdateWorker).not_to receive(:perform_async) + + described_class.new.perform(123) + end + end +end diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index c0427639746..4d3fdbd9554 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do +describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate do describe '#perform' do - set(:merge_request) { create(:merge_request) } - set(:merge_request_diff) { merge_request.merge_request_diff } + let(:merge_request) { create(:merge_request) } + let(:merge_request_diff) { merge_request.merge_request_diff } let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } def diffs_to_hashes(diffs) @@ -31,8 +31,8 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do end it 'creates correct entries in the merge_request_diff_commits table' do - expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(commits.count) - expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(commits) + expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(expected_commits.count) + expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(expected_commits) end it 'creates correct entries in the merge_request_diff_files table' do @@ -70,8 +70,8 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do before do merge_request.reload_diff(true) - convert_to_yaml(start_id, merge_request_diff.commits, merge_request_diff.diffs) - convert_to_yaml(stop_id, updated_merge_request_diff.commits, updated_merge_request_diff.diffs) + convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files)) + convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files)) MergeRequestDiffCommit.delete_all MergeRequestDiffFile.delete_all @@ -80,10 +80,32 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when BUFFER_ROWS is exceeded' do before do stub_const("#{described_class}::BUFFER_ROWS", 1) + + allow(Gitlab::Database).to receive(:bulk_insert).and_call_original end - it 'updates and continues' do - expect(described_class::MergeRequestDiff).to receive(:transaction).twice + it 'inserts commit rows in chunks of BUFFER_ROWS' do + # There are 29 commits in each diff, so we should have slices of 20 + 9 + 20 + 9. + stub_const("#{described_class}::BUFFER_ROWS", 20) + + expect(Gitlab::Database).to receive(:bulk_insert) + .with('merge_request_diff_commits', anything) + .exactly(4) + .times + .and_call_original + + subject.perform(start_id, stop_id) + end + + it 'inserts diff rows in chunks of DIFF_FILE_BUFFER_ROWS' do + # There are 20 files in each diff, so we should have slices of 20 + 20. + stub_const("#{described_class}::DIFF_FILE_BUFFER_ROWS", 20) + + expect(Gitlab::Database).to receive(:bulk_insert) + .with('merge_request_diff_files', anything) + .exactly(2) + .times + .and_call_original subject.perform(start_id, stop_id) end @@ -91,32 +113,102 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when BUFFER_ROWS is not exceeded' do it 'only updates once' do - expect(described_class::MergeRequestDiff).to receive(:transaction).once + expect(Gitlab::Database).to receive(:bulk_insert) + .with('merge_request_diff_commits', anything) + .once + .and_call_original + + expect(Gitlab::Database).to receive(:bulk_insert) + .with('merge_request_diff_files', anything) + .once + .and_call_original subject.perform(start_id, stop_id) end end - end - context 'when the merge request diff update fails' do - before do - allow(described_class::MergeRequestDiff) - .to receive(:update_all).and_raise(ActiveRecord::Rollback) - end + context 'when some rows were already inserted due to a previous failure' do + before do + subject.perform(start_id, stop_id) - it 'does not add any diff commits' do - expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } - .not_to change { MergeRequestDiffCommit.count } + convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files)) + convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files)) + end + + it 'does not raise' do + expect { subject.perform(start_id, stop_id) }.not_to raise_exception + end + + it 'logs a message' do + expect(Rails.logger).to receive(:info) + .with( + a_string_matching(described_class.name).and(matching([start_id, stop_id].inspect)) + ) + .twice + + subject.perform(start_id, stop_id) + end + + it 'ends up with the correct rows' do + expect(updated_merge_request_diff.commits.count).to eq(29) + expect(updated_merge_request_diff.raw_diffs.count).to eq(20) + end end - it 'does not add any diff files' do - expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } - .not_to change { MergeRequestDiffFile.count } + context 'when the merge request diff update fails' do + let(:exception) { ActiveRecord::RecordNotFound } + + let(:perform_ignoring_exceptions) do + begin + subject.perform(start_id, stop_id) + rescue described_class::Error + end + end + + before do + allow_any_instance_of(described_class::MergeRequestDiff::ActiveRecord_Relation) + .to receive(:update_all).and_raise(exception) + end + + it 'raises an error' do + expect { subject.perform(start_id, stop_id) } + .to raise_exception(described_class::Error) + end + + it 'logs the error' do + expect(Rails.logger).to receive(:info).with( + a_string_matching(described_class.name) + .and(matching([start_id, stop_id].inspect)) + .and(matching(exception.name)) + ) + + perform_ignoring_exceptions + end + + it 'still adds diff commits' do + expect { perform_ignoring_exceptions } + .to change { MergeRequestDiffCommit.count } + end + + it 'still adds diff files' do + expect { perform_ignoring_exceptions } + .to change { MergeRequestDiffFile.count } + end end end context 'when the merge request diff has valid commits and diffs' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } + let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } + let(:expected_diffs) { diffs } + + include_examples 'updated MR diff' + end + + context 'when the merge request diff has diffs but no commits' do + let(:commits) { nil } + let(:expected_commits) { [] } let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:expected_diffs) { diffs } @@ -125,6 +217,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs do not have too_large set' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:diffs) do @@ -136,6 +229,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs do not have a_mode and b_mode set' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } let(:diffs) do @@ -147,6 +241,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs have binary content' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:expected_diffs) { diffs } # The start of a PDF created by Illustrator @@ -175,6 +270,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diff has commits, but no diffs' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:diffs) { [] } let(:expected_diffs) { diffs } @@ -183,6 +279,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs have invalid content' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } + let(:expected_commits) { commits } let(:diffs) { ['--broken-diff'] } let(:expected_diffs) { [] } @@ -192,6 +289,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs are Rugged::Patch instances' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:expected_commits) { commits } let(:diffs) { first_commit.rugged_diff_from_parent.patches } let(:expected_diffs) { [] } @@ -201,6 +299,7 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits do context 'when the merge request diffs are Rugged::Diff::Delta instances' do let(:commits) { merge_request_diff.commits.map(&:to_hash) } let(:first_commit) { merge_request.project.repository.commit(merge_request_diff.head_commit_sha) } + let(:expected_commits) { commits } let(:diffs) { first_commit.rugged_diff_from_parent.deltas } let(:expected_diffs) { [] } diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb index 59f69d1e4b1..7b5a00c6111 100644 --- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do end describe '#perform' do - it 'renames the path of system-uploads', truncate: true do + it 'renames the path of system-uploads', :truncate do upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg') migration.perform('uploads/system/', 'uploads/-/system/') diff --git a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb new file mode 100644 index 00000000000..dfbf1bb681a --- /dev/null +++ b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::NormalizeLdapExternUidsRange, :migration, schema: 20170921101004 do + let!(:identities) { table(:identities) } + + before do + # LDAP identities + (1..4).each do |i| + identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i) + end + + # Non-LDAP identity + identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5) + + # Another LDAP identity + identities.create!(id: 6, provider: 'ldapmain', extern_uid: " uid = foo 6, ou = People, dc = example, dc = com ", user_id: 6) + end + + it 'normalizes the LDAP identities in the range' do + described_class.new.perform(1, 3) + expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") + expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") + expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") + expect(identities.find(4).extern_uid).to eq(" uid = foo 4, ou = People, dc = example, dc = com ") + expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") + expect(identities.find(6).extern_uid).to eq(" uid = foo 6, ou = People, dc = example, dc = com ") + + described_class.new.perform(4, 6) + expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") + expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") + expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") + expect(identities.find(4).extern_uid).to eq("uid=foo 4,ou=people,dc=example,dc=com") + expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") + expect(identities.find(6).extern_uid).to eq("uid=foo 6,ou=people,dc=example,dc=com") + end +end diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb new file mode 100644 index 00000000000..2c2684a6fc9 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do + let(:migration) { described_class.new } + let(:base1) { create(:project) } + let(:base1_fork1) { create(:project) } + let(:base1_fork2) { create(:project) } + + let(:base2) { create(:project) } + let(:base2_fork1) { create(:project) } + let(:base2_fork2) { create(:project) } + + let!(:forked_project_links) { table(:forked_project_links) } + let!(:fork_networks) { table(:fork_networks) } + let!(:fork_network_members) { table(:fork_network_members) } + + let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } + let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } + + before do + # A normal fork link + forked_project_links.create(id: 1, + forked_from_project_id: base1.id, + forked_to_project_id: base1_fork1.id) + forked_project_links.create(id: 2, + forked_from_project_id: base1.id, + forked_to_project_id: base1_fork2.id) + + forked_project_links.create(id: 3, + forked_from_project_id: base2.id, + forked_to_project_id: base2_fork1.id) + forked_project_links.create(id: 4, + forked_from_project_id: base2_fork1.id, + forked_to_project_id: create(:project).id) + + forked_project_links.create(id: 5, + forked_from_project_id: base2.id, + forked_to_project_id: base2_fork2.id) + + migration.perform(1, 3) + end + + it 'it creates the fork network' do + expect(fork_network1).not_to be_nil + expect(fork_network2).not_to be_nil + end + + it 'does not create a fork network for a fork-of-fork' do + # perfrom the entire batch + migration.perform(1, 5) + + expect(fork_networks.find_by(root_project_id: base2_fork1.id)).to be_nil + end + + it 'creates memberships for the root of fork networks' do + base1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, + project_id: base1.id) + base2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, + project_id: base2.id) + + expect(base1_membership).not_to be_nil + expect(base2_membership).not_to be_nil + end + + it 'skips links that had their source project deleted' do + forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: create(:project).id) + + migration.perform(5, 8) + + expect(fork_networks.find_by(root_project_id: 99999)).to be_nil + end + + it 'schedules a job for inserting memberships for forks-of-forks' do + delay = Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY + + expect(BackgroundMigrationWorker) + .to receive(:perform_in).with(delay, "CreateForkNetworkMembershipsRange", [1, 3]) + + migration.perform(1, 3) + end + + it 'only processes a single batch of links at a time' do + expect(fork_network_members.count).to eq(5) + + migration.perform(3, 5) + + expect(fork_network_members.count).to eq(7) + end + + it 'can be repeated without effect' do + expect { migration.perform(1, 3) }.not_to change { fork_network_members.count } + end +end diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb index 422f2af7266..b68301a066a 100644 --- a/spec/lib/gitlab/backup/manager_spec.rb +++ b/spec/lib/gitlab/backup/manager_spec.rb @@ -172,10 +172,6 @@ describe Backup::Manager do end describe '#unpack' do - before do - allow(Dir).to receive(:chdir) - end - context 'when there are no backup files in the directory' do before do allow(Dir).to receive(:glob).and_return([]) diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb index 6c25b7349e1..74a24a4424b 100644 --- a/spec/lib/gitlab/checks/change_access_spec.rb +++ b/spec/lib/gitlab/checks/change_access_spec.rb @@ -11,13 +11,13 @@ describe Gitlab::Checks::ChangeAccess do let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } } let(:protocol) { 'ssh' } - subject do + subject(:change_access) do described_class.new( changes, project: project, user_access: user_access, protocol: protocol - ).exec + ) end before do @@ -26,7 +26,7 @@ describe Gitlab::Checks::ChangeAccess do context 'without failed checks' do it "doesn't raise an error" do - expect { subject }.not_to raise_error + expect { subject.exec }.not_to raise_error end end @@ -34,7 +34,7 @@ describe Gitlab::Checks::ChangeAccess do it 'raises an error' do expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') end end @@ -45,7 +45,7 @@ describe Gitlab::Checks::ChangeAccess do allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false) - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') end context 'with protected tag' do @@ -61,7 +61,7 @@ describe Gitlab::Checks::ChangeAccess do let(:newrev) { '0000000000000000000000000000000000000000' } it 'is prevented' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/) + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/) end end @@ -70,7 +70,7 @@ describe Gitlab::Checks::ChangeAccess do let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } it 'is prevented' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/) + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/) end end end @@ -81,14 +81,14 @@ describe Gitlab::Checks::ChangeAccess do let(:ref) { 'refs/tags/v9.1.0' } it 'prevents creation below access level' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/) + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/) end context 'when user has access' do let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') } it 'allows tag creation' do - expect { subject }.not_to raise_error + expect { subject.exec }.not_to raise_error end end end @@ -101,7 +101,7 @@ describe Gitlab::Checks::ChangeAccess do let(:ref) { 'refs/heads/master' } it 'raises an error' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.') end end @@ -114,7 +114,7 @@ describe Gitlab::Checks::ChangeAccess do it 'raises an error if the user is not allowed to do forced pushes to protected branches' do expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.') end it 'raises an error if the user is not allowed to merge to protected branches' do @@ -122,13 +122,13 @@ describe Gitlab::Checks::ChangeAccess do expect(user_access).to receive(:can_merge_to_branch?).and_return(false) expect(user_access).to receive(:can_push_to_branch?).and_return(false) - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.') end it 'raises an error if the user is not allowed to push to protected branches' do expect(user_access).to receive(:can_push_to_branch?).and_return(false) - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.') end context 'branch deletion' do @@ -137,7 +137,7 @@ describe Gitlab::Checks::ChangeAccess do context 'if the user is not allowed to delete protected branches' do it 'raises an error' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.') end end @@ -150,18 +150,63 @@ describe Gitlab::Checks::ChangeAccess do let(:protocol) { 'web' } it 'allows branch deletion' do - expect { subject }.not_to raise_error + expect { subject.exec }.not_to raise_error end end context 'over SSH or HTTP' do it 'raises an error' do - expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.') end end end end end end + + context 'LFS integrity check' do + let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') } + + before do + allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects) do |&lazy_block| + lazy_block.call([blob_object.id]) + end + end + + context 'with LFS not enabled' do + it 'skips integrity check' do + expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects) + + subject.exec + end + end + + context 'with LFS enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + context 'deletion' do + let(:changes) { { oldrev: oldrev, ref: ref } } + + it 'skips integrity check' do + expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects) + + subject.exec + end + end + + it 'fails if any LFS blobs are missing' do + expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /LFS objects are missing/) + end + + it 'succeeds if LFS objects have already been uploaded' do + lfs_object = create(:lfs_object, oid: blob_object.lfs_oid) + create(:lfs_objects_project, project: project, lfs_object: lfs_object) + + expect { subject.exec }.not_to raise_error + end + end + end end end diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index 2c7ef622c51..633e319f46d 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Checks::ForcePush do let(:project) { create(:project, :repository) } - context "exit code checking", skip_gitaly_mock: true do + context "exit code checking", :skip_gitaly_mock do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0]) diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index e6645985ba4..33540eab5d6 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -195,6 +195,32 @@ describe Gitlab::Ci::Ansi2html do end end + context "with section markers" do + let(:section_name) { 'test_section' } + let(:section_start_time) { Time.new(2017, 9, 20).utc } + let(:section_duration) { 3.seconds } + let(:section_end_time) { section_start_time + section_duration } + let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} + let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} + let(:section_start_html) do + '<div class="hidden" data-action="start"'\ + " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{section_name}\">"\ + "#{section_start[0...-5]}</div>" + end + let(:section_end_html) do + '<div class="hidden" data-action="end"'\ + " data-timestamp=\"#{section_end_time.to_i}\" data-section=\"#{section_name}\">"\ + "#{section_end[0...-5]}</div>" + end + + it "prints light red" do + text = "#{section_start}\e[91mHello\e[0m\n#{section_end}" + html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}} + + expect(convert_html(text)).to eq(html) + end + end + describe "truncates" do let(:text) { "Hello World" } let(:stream) { StringIO.new(text) } diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 809fda11879..2a3f7807fdb 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -77,8 +77,20 @@ describe Gitlab::Ci::CronParser do it_behaves_like "returns time in the future" - it 'converts time in server time zone' do - expect(subject.hour).to eq(hour_in_utc) + context 'when PST (Pacific Standard Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 1, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end + end + + context 'when PDT (Pacific Daylight Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 6, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end end end end @@ -100,8 +112,20 @@ describe Gitlab::Ci::CronParser do it_behaves_like "returns time in the future" - it 'converts time in server time zone' do - expect(subject.hour).to eq(hour_in_utc) + context 'when CET (Central European Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 1, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end + end + + context 'when CEST (Central European Summer Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 6, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end end end @@ -111,8 +135,20 @@ describe Gitlab::Ci::CronParser do it_behaves_like "returns time in the future" - it 'converts time in server time zone' do - expect(subject.hour).to eq(hour_in_utc) + context 'when EST (Eastern Standard Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 1, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end + end + + context 'when EDT (Eastern Daylight Time)' do + it 'converts time in server time zone' do + Timecop.freeze(Time.utc(2017, 6, 1)) do + expect(subject.hour).to eq(hour_in_utc) + end + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb new file mode 100644 index 00000000000..f54e2326b06 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Create do + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:pipeline) do + build(:ci_pipeline_with_one_job, project: project, + ref: 'master') + end + + let(:command) do + double('command', project: project, + current_user: user, + seeds_block: nil) + end + + let(:step) { described_class.new(pipeline, command) } + + before do + step.perform! + end + + context 'when pipeline is ready to be saved' do + it 'saves a pipeline' do + expect(pipeline).to be_persisted + end + + it 'does not break the chain' do + expect(step.break?).to be false + end + + it 'creates stages' do + expect(pipeline.reload.stages).to be_one + end + end + + context 'when pipeline has validation errors' do + let(:pipeline) do + build(:ci_pipeline, project: project, ref: nil) + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'appends validation error' do + expect(pipeline.errors.to_a) + .to include /Failed to persist the pipeline/ + end + end + + context 'when there is a seed block present' do + let(:seeds) { spy('pipeline seeds') } + + let(:command) do + double('command', project: project, + current_user: user, + seeds_block: seeds) + end + + it 'executes the block' do + expect(seeds).to have_received(:call).with(pipeline) + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb new file mode 100644 index 00000000000..e165e0fac2a --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Sequence do + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:pipeline) { build_stubbed(:ci_pipeline) } + let(:command) { double('command' ) } + let(:first_step) { spy('first step') } + let(:second_step) { spy('second step') } + let(:sequence) { [first_step, second_step] } + + subject do + described_class.new(pipeline, command, sequence) + end + + context 'when one of steps breaks the chain' do + before do + allow(first_step).to receive(:break?).and_return(true) + end + + it 'does not process the second step' do + subject.build! do |pipeline, sequence| + expect(sequence).not_to be_complete + end + + expect(second_step).not_to have_received(:perform!) + end + + it 'returns a pipeline object' do + expect(subject.build!).to eq pipeline + end + end + + context 'when all chains are executed correctly' do + before do + sequence.each do |step| + allow(step).to receive(:break?).and_return(false) + end + end + + it 'iterates through entire sequence' do + subject.build! do |pipeline, sequence| + expect(sequence).to be_complete + end + + expect(first_step).to have_received(:perform!) + expect(second_step).to have_received(:perform!) + end + + it 'returns a pipeline object' do + expect(subject.build!).to eq pipeline + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb new file mode 100644 index 00000000000..32bd5de829b --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Skip do + set(:project) { create(:project) } + set(:user) { create(:user) } + set(:pipeline) { create(:ci_pipeline, project: project) } + + let(:command) do + double('command', project: project, + current_user: user, + ignore_skip_ci: false, + save_incompleted: true) + end + + let(:step) { described_class.new(pipeline, command) } + + context 'when pipeline has been skipped by a user' do + before do + allow(pipeline).to receive(:git_commit_message) + .and_return('commit message [ci skip]') + + step.perform! + end + + it 'should break the chain' do + expect(step.break?).to be true + end + + it 'skips the pipeline' do + expect(pipeline.reload).to be_skipped + end + end + + context 'when pipeline has not been skipped' do + before do + step.perform! + end + + it 'should not break the chain' do + expect(step.break?).to be false + end + + it 'should not skip a pipeline chain' do + expect(pipeline.reload).not_to be_skipped + end + end + + context 'when [ci skip] should be ignored' do + let(:command) do + double('command', project: project, + current_user: user, + ignore_skip_ci: true) + end + + it 'does not break the chain' do + step.perform! + + expect(step.break?).to be false + end + end + + context 'when pipeline should be skipped but not persisted' do + let(:command) do + double('command', project: project, + current_user: user, + ignore_skip_ci: false, + save_incompleted: false) + end + + before do + allow(pipeline).to receive(:git_commit_message) + .and_return('commit message [ci skip]') + + step.perform! + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'does not skip pipeline' do + expect(pipeline.reload).not_to be_skipped + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb new file mode 100644 index 00000000000..0bbdd23f4d6 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + + let(:pipeline) do + build_stubbed(:ci_pipeline, ref: ref, project: project) + end + + let(:command) do + double('command', project: project, current_user: user) + end + + let(:step) { described_class.new(pipeline, command) } + + let(:ref) { 'master' } + + context 'when users has no ability to run a pipeline' do + before do + step.perform! + end + + it 'adds an error about insufficient permissions' do + expect(pipeline.errors.to_a) + .to include /Insufficient permissions/ + end + + it 'breaks the pipeline builder chain' do + expect(step.break?).to eq true + end + end + + context 'when user has ability to create a pipeline' do + before do + project.add_developer(user) + + step.perform! + end + + it 'does not invalidate the pipeline' do + expect(pipeline).to be_valid + end + + it 'does not break the chain' do + expect(step.break?).to eq false + end + end + + describe '#allowed_to_create?' do + subject { step.allowed_to_create? } + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it { is_expected.to be_truthy } + + context 'when the branch is protected' do + let!(:protected_branch) do + create(:protected_branch, project: project, name: ref) + end + + it { is_expected.to be_falsey } + + context 'when developers are allowed to merge' do + let!(:protected_branch) do + create(:protected_branch, + :developers_can_merge, + project: project, + name: ref) + end + + it { is_expected.to be_truthy } + end + end + + context 'when the tag is protected' do + let(:ref) { 'v1.0.0' } + + let!(:protected_tag) do + create(:protected_tag, project: project, name: ref) + end + + it { is_expected.to be_falsey } + + context 'when developers are allowed to create the tag' do + let!(:protected_tag) do + create(:protected_tag, + :developers_can_create, + project: project, + name: ref) + end + + it { is_expected.to be_truthy } + end + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it { is_expected.to be_truthy } + + context 'when the branch is protected' do + let!(:protected_branch) do + create(:protected_branch, project: project, name: ref) + end + + it { is_expected.to be_truthy } + end + + context 'when the tag is protected' do + let(:ref) { 'v1.0.0' } + + let!(:protected_tag) do + create(:protected_tag, project: project, name: ref) + end + + it { is_expected.to be_truthy } + + context 'when no one can create the tag' do + let!(:protected_tag) do + create(:protected_tag, + :no_one_can_create, + project: project, + name: ref) + end + + it { is_expected.to be_falsey } + end + end + end + + context 'when owner cannot create pipeline' do + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb new file mode 100644 index 00000000000..8357af38f92 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Validate::Config do + set(:project) { create(:project) } + set(:user) { create(:user) } + + let(:command) do + double('command', project: project, + current_user: user, + save_incompleted: true) + end + + let!(:step) { described_class.new(pipeline, command) } + + before do + step.perform! + end + + context 'when pipeline has no YAML configuration' do + let(:pipeline) do + build_stubbed(:ci_pipeline, project: project) + end + + it 'appends errors about missing configuration' do + expect(pipeline.errors.to_a) + .to include 'Missing .gitlab-ci.yml file' + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + end + + context 'when YAML configuration contains errors' do + let(:pipeline) do + build(:ci_pipeline, project: project, config: 'invalid YAML') + end + + it 'appends errors about YAML errors' do + expect(pipeline.errors.to_a) + .to include 'Invalid configuration format' + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + context 'when saving incomplete pipeline is allowed' do + let(:command) do + double('command', project: project, + current_user: user, + save_incompleted: true) + end + + it 'fails the pipeline' do + expect(pipeline.reload).to be_failed + end + + it 'sets a config error failure reason' do + expect(pipeline.reload.config_error?).to eq true + end + end + + context 'when saving incomplete pipeline is not allowed' do + let(:command) do + double('command', project: project, + current_user: user, + save_incompleted: false) + end + + it 'does not drop pipeline' do + expect(pipeline).not_to be_failed + expect(pipeline).not_to be_persisted + end + end + end + + context 'when pipeline has no stages / jobs' do + let(:config) do + { rspec: { + script: 'ls', + only: ['something'] + } } + end + + let(:pipeline) do + build(:ci_pipeline, project: project, config: config) + end + + it 'appends an error about missing stages' do + expect(pipeline.errors.to_a) + .to include 'No stages / jobs for this pipeline.' + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + end + + context 'when pipeline contains configuration validation errors' do + let(:config) { { rspec: {} } } + + let(:pipeline) do + build(:ci_pipeline, project: project, config: config) + end + + it 'appends configuration validation errors to pipeline errors' do + expect(pipeline.errors.to_a) + .to include "jobs:rspec config can't be blank" + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + end + + context 'when pipeline is correct and complete' do + let(:pipeline) do + build(:ci_pipeline_with_one_job, project: project) + end + + it 'does not invalidate the pipeline' do + expect(pipeline).to be_valid + end + + it 'does not break the chain' do + expect(step.break?).to be false + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb new file mode 100644 index 00000000000..bb356efe9ad --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + + let(:command) do + double('command', project: project, current_user: user) + end + + let!(:step) { described_class.new(pipeline, command) } + + before do + step.perform! + end + + context 'when pipeline ref and sha exists' do + let(:pipeline) do + build_stubbed(:ci_pipeline, ref: 'master', sha: '123', project: project) + end + + it 'does not break the chain' do + expect(step.break?).to be false + end + + it 'does not append pipeline errors' do + expect(pipeline.errors).to be_empty + end + end + + context 'when pipeline ref does not exist' do + let(:pipeline) do + build_stubbed(:ci_pipeline, ref: 'something', project: project) + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'adds an error about missing ref' do + expect(pipeline.errors.to_a) + .to include 'Reference not found' + end + end + + context 'when pipeline does not have SHA set' do + let(:pipeline) do + build_stubbed(:ci_pipeline, ref: 'master', sha: nil, project: project) + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'adds an error about missing SHA' do + expect(pipeline.errors.to_a) + .to include 'Commit not found' + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb index b26728a843c..7c9836e2da6 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Ci::PipelineDuration do +describe Gitlab::Ci::Pipeline::Duration do let(:calculated_duration) { calculate(data) } shared_examples 'calculating duration' do @@ -107,9 +107,9 @@ describe Gitlab::Ci::PipelineDuration do def calculate(data) periods = data.shuffle.map do |(first, last)| - Gitlab::Ci::PipelineDuration::Period.new(first, last) + described_class::Period.new(first, last) end - Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first)) + described_class.from_periods(periods.sort_by(&:first)) end end diff --git a/spec/lib/gitlab/ci/stage/seed_spec.rb b/spec/lib/gitlab/ci/stage/seed_spec.rb index 9ecd128faca..3fe8d50c49a 100644 --- a/spec/lib/gitlab/ci/stage/seed_spec.rb +++ b/spec/lib/gitlab/ci/stage/seed_spec.rb @@ -11,6 +11,12 @@ describe Gitlab::Ci::Stage::Seed do described_class.new(pipeline, 'test', builds) end + describe '#size' do + it 'returns a number of jobs in the stage' do + expect(subject.size).to eq 2 + end + end + describe '#stage' do it 'returns hash attributes of a stage' do expect(subject.stage).to be_a Hash diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 5a7a42d84c0..9cdebaa5cf2 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -66,7 +66,7 @@ describe Gitlab::Ci::Status::Build::Cancelable do end describe '#action_icon' do - it { expect(subject.action_icon).to eq 'icon_action_cancel' } + it { expect(subject.action_icon).to eq 'cancel' } end describe '#action_title' do diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index 8768302eda1..2b32e47e9ba 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'passed' - expect(status.icon).to eq 'icon_status_success' + expect(status.icon).to eq 'status_success' expect(status.favicon).to eq 'favicon_status_success' expect(status.label).to eq 'passed' expect(status).to have_details @@ -57,7 +57,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'failed' - expect(status.icon).to eq 'icon_status_failed' + expect(status.icon).to eq 'status_failed' expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed' expect(status).to have_details @@ -84,7 +84,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'failed' - expect(status.icon).to eq 'icon_status_warning' + expect(status.icon).to eq 'warning' expect(status.favicon).to eq 'favicon_status_failed' expect(status.label).to eq 'failed (allowed to fail)' expect(status).to have_details @@ -113,7 +113,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'canceled' - expect(status.icon).to eq 'icon_status_canceled' + expect(status.icon).to eq 'status_canceled' expect(status.favicon).to eq 'favicon_status_canceled' expect(status.label).to eq 'canceled' expect(status).to have_details @@ -139,7 +139,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'running' - expect(status.icon).to eq 'icon_status_running' + expect(status.icon).to eq 'status_running' expect(status.favicon).to eq 'favicon_status_running' expect(status.label).to eq 'running' expect(status).to have_details @@ -165,7 +165,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'pending' - expect(status.icon).to eq 'icon_status_pending' + expect(status.icon).to eq 'status_pending' expect(status.favicon).to eq 'favicon_status_pending' expect(status.label).to eq 'pending' expect(status).to have_details @@ -190,7 +190,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'skipped' - expect(status.icon).to eq 'icon_status_skipped' + expect(status.icon).to eq 'status_skipped' expect(status.favicon).to eq 'favicon_status_skipped' expect(status.label).to eq 'skipped' expect(status).to have_details @@ -219,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'manual' expect(status.group).to eq 'manual' - expect(status.icon).to eq 'icon_status_manual' + expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' expect(status.label).to include 'manual play action' expect(status).to have_details @@ -274,7 +274,7 @@ describe Gitlab::Ci::Status::Build::Factory do it 'fabricates status with correct details' do expect(status.text).to eq 'manual' expect(status.group).to eq 'manual' - expect(status.icon).to eq 'icon_status_manual' + expect(status.icon).to eq 'status_manual' expect(status.favicon).to eq 'favicon_status_manual' expect(status.label).to eq 'manual stop action (not allowed)' expect(status).to have_details diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb index 20f71459738..79a65fc67e8 100644 --- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb +++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Build::FailedAllowed do describe '#icon' do it 'returns a warning icon' do - expect(subject.icon).to eq 'icon_status_warning' + expect(subject.icon).to eq 'warning' end end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 32b2e62e4e0..81d5f553fd1 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -46,7 +46,7 @@ describe Gitlab::Ci::Status::Build::Play do end describe '#action_icon' do - it { expect(subject.action_icon).to eq 'icon_action_play' } + it { expect(subject.action_icon).to eq 'play' } end describe '#action_title' do diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index 21026f2c968..14d42e0d70f 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -66,7 +66,7 @@ describe Gitlab::Ci::Status::Build::Retryable do end describe '#action_icon' do - it { expect(subject.action_icon).to eq 'icon_action_retry' } + it { expect(subject.action_icon).to eq 'retry' } end describe '#action_title' do diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index e0425103f41..18e250772f0 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::Ci::Status::Build::Stop do end describe '#action_icon' do - it { expect(subject.action_icon).to eq 'icon_action_stop' } + it { expect(subject.action_icon).to eq 'stop' } end describe '#action_title' do diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 530639a5897..dc74d7e28c5 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Canceled do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_canceled' } + it { expect(subject.icon).to eq 'status_canceled' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index aef982e17f1..ce4333f2aca 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Created do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_created' } + it { expect(subject.icon).to eq 'status_created' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 9a25743885c..a4a92117c7f 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Failed do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_failed' } + it { expect(subject.icon).to eq 'status_failed' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb index 6fdc3801d71..0463f2e1aff 100644 --- a/spec/lib/gitlab/ci/status/manual_spec.rb +++ b/spec/lib/gitlab/ci/status/manual_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Manual do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_manual' } + it { expect(subject.icon).to eq 'status_manual' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index ffc53f0506b..0e25358dd8a 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Pending do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_pending' } + it { expect(subject.icon).to eq 'status_pending' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index 0babf1fb54e..9c9d431bb5d 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Running do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_running' } + it { expect(subject.icon).to eq 'status_running' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 670747c9f0b..63694ca0ea6 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Skipped do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_skipped' } + it { expect(subject.icon).to eq 'status_skipped' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index ff65b074808..2f67df71c4f 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Success do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_success' } + it { expect(subject.icon).to eq 'status_success' } end describe '#favicon' do diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb index 7e2269397c6..4582354e739 100644 --- a/spec/lib/gitlab/ci/status/success_warning_spec.rb +++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::SuccessWarning do end describe '#icon' do - it { expect(subject.icon).to eq 'icon_status_warning' } + it { expect(subject.icon).to eq 'status_warning' } end describe '#group' do diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb new file mode 100644 index 00000000000..ca53ff87c6f --- /dev/null +++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Gitlab::Ci::Trace::SectionParser do + def lines_with_pos(text) + pos = 0 + StringIO.new(text).each_line do |line| + yield line, pos + pos += line.bytesize + 1 # newline + end + end + + def build_lines(text) + to_enum(:lines_with_pos, text) + end + + def section(name, start, duration, text) + end_ = start + duration + "section_start:#{start.to_i}:#{name}\r\033[0K#{text}section_end:#{end_.to_i}:#{name}\r\033[0K" + end + + let(:lines) { build_lines('') } + subject { described_class.new(lines) } + + describe '#sections' do + before do + subject.parse! + end + + context 'empty trace' do + let(:lines) { build_lines('') } + + it { expect(subject.sections).to be_empty } + end + + context 'with a sectionless trace' do + let(:lines) { build_lines("line 1\nline 2\n") } + + it { expect(subject.sections).to be_empty } + end + + context 'with trace markers' do + let(:start_time) { Time.new(2017, 10, 5).utc } + let(:section_b_duration) { 1.second } + let(:section_a) { section('a', start_time, 0, 'a line') } + let(:section_b) { section('b', start_time, section_b_duration, "another line\n") } + let(:lines) { build_lines(section_a + section_b) } + + it { expect(subject.sections.size).to eq(2) } + it { expect(subject.sections[1][:name]).to eq('b') } + it { expect(subject.sections[1][:date_start]).to eq(start_time) } + it { expect(subject.sections[1][:date_end]).to eq(start_time + section_b_duration) } + end + end + + describe '#parse!' do + context 'multiple "section_" but no complete markers' do + let(:lines) { build_lines('section_section_section_') } + + it 'must find 3 possible section start but no complete sections' do + expect(subject).to receive(:find_next_marker).exactly(3).times.and_call_original + + subject.parse! + + expect(subject.sections).to be_empty + end + end + + context 'trace with UTF-8 chars' do + let(:line) { 'GitLab â¤ï¸ 狸 (tanukis)\n' } + let(:trace) { section('test_section', Time.new(2017, 10, 5).utc, 3.seconds, line) } + let(:lines) { build_lines(trace) } + + it 'must handle correctly byte positioning' do + expect(subject).to receive(:find_next_marker).exactly(2).times.and_call_original + + subject.parse! + + sections = subject.sections + + expect(sections.size).to eq(1) + s = sections[0] + len = s[:byte_end] - s[:byte_start] + expect(trace.byteslice(s[:byte_start], len)).to eq(line) + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 9cb0b62590a..3546532b9b4 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -61,6 +61,93 @@ describe Gitlab::Ci::Trace do end end + describe '#extract_sections' do + let(:log) { 'No sections' } + let(:sections) { trace.extract_sections } + + before do + trace.set(log) + end + + context 'no sections' do + it 'returs []' do + expect(trace.extract_sections).to eq([]) + end + end + + context 'multiple sections available' do + let(:log) { File.read(expand_fixture_path('trace/trace_with_sections')) } + let(:sections_data) do + [ + { name: 'prepare_script', lines: 2, duration: 3.seconds }, + { name: 'get_sources', lines: 4, duration: 1.second }, + { name: 'restore_cache', lines: 0, duration: 0.seconds }, + { name: 'download_artifacts', lines: 0, duration: 0.seconds }, + { name: 'build_script', lines: 2, duration: 1.second }, + { name: 'after_script', lines: 0, duration: 0.seconds }, + { name: 'archive_cache', lines: 0, duration: 0.seconds }, + { name: 'upload_artifacts', lines: 0, duration: 0.seconds } + ] + end + + it "returns valid sections" do + expect(sections).not_to be_empty + expect(sections.size).to eq(sections_data.size), + "expected #{sections_data.size} sections, got #{sections.size}" + + buff = StringIO.new(log) + sections.each_with_index do |s, i| + expected = sections_data[i] + + expect(s[:name]).to eq(expected[:name]) + expect(s[:date_end] - s[:date_start]).to eq(expected[:duration]) + + buff.seek(s[:byte_start], IO::SEEK_SET) + length = s[:byte_end] - s[:byte_start] + lines = buff.read(length).count("\n") + expect(lines).to eq(expected[:lines]) + end + end + end + + context 'logs contains "section_start"' do + let(:log) { "section_start:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_end:1506417477:a_section\r\033[0K"} + + it "returns only one section" do + expect(sections).not_to be_empty + expect(sections.size).to eq(1) + + section = sections[0] + expect(section[:name]).to eq('a_section') + expect(section[:byte_start]).not_to eq(section[:byte_end]), "got an empty section" + end + end + + context 'missing section_end' do + let(:log) { "section_start:1506417476:a_section\r\033[0KSome logs\nNo section_end\n"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + + context 'missing section_start' do + let(:log) { "Some logs\nNo section_start\nsection_end:1506417476:a_section\r\033[0K"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + + context 'inverted section_start section_end' do + let(:log) { "section_end:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_start:1506417477:a_section\r\033[0K"} + + it "returns no sections" do + expect(sections).to be_empty + end + end + end + describe '#set' do before do trace.set("12") diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 9e528392756..ef7d766a13d 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -254,6 +254,46 @@ describe Gitlab::ClosingIssueExtractor do expect(subject.closed_by_message(message)).to eq([issue]) end + it do + message = "Implement: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Implements: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Implemented: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Implementing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "implement: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "implements: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "implemented: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "implementing: #{reference}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + context 'with an external issue tracker reference' do it 'extracts the referenced issue' do jira_project = create(:jira_project, name: 'JIRA_EXT1') diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb index a4d7628b03a..5944ce8049a 100644 --- a/spec/lib/gitlab/conflict/file_collection_spec.rb +++ b/spec/lib/gitlab/conflict/file_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Conflict::FileCollection do let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') } - let(:file_collection) { described_class.read_only(merge_request) } + let(:file_collection) { described_class.new(merge_request) } describe '#files' do it 'returns an array of Conflict::Files' do diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 5356e9742b4..bf981d2f6f6 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -8,9 +8,10 @@ describe Gitlab::Conflict::File do let(:our_commit) { rugged.branches['conflict-resolvable'].target } let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) } let(:index) { rugged.merge_commits(our_commit, their_commit) } - let(:conflict) { index.conflicts.last } - let(:merge_file_result) { index.merge_file('files/ruby/regex.rb') } - let(:conflict_file) { described_class.new(merge_file_result, conflict, merge_request: merge_request) } + let(:rugged_conflict) { index.conflicts.last } + let(:raw_conflict_content) { index.merge_file('files/ruby/regex.rb')[:data] } + let(:raw_conflict_file) { Gitlab::Git::Conflict::File.new(repository, our_commit.oid, rugged_conflict, raw_conflict_content) } + let(:conflict_file) { described_class.new(raw_conflict_file, merge_request: merge_request) } describe '#resolve_lines' do let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact } @@ -48,18 +49,18 @@ describe Gitlab::Conflict::File do end end - it 'raises MissingResolution when passed a hash without resolutions for all sections' do + it 'raises ResolutionError when passed a hash without resolutions for all sections' do empty_hash = section_keys.map { |key| [key, nil] }.to_h invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h expect { conflict_file.resolve_lines({}) } - .to raise_error(Gitlab::Conflict::File::MissingResolution) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) expect { conflict_file.resolve_lines(empty_hash) } - .to raise_error(Gitlab::Conflict::File::MissingResolution) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) expect { conflict_file.resolve_lines(invalid_hash) } - .to raise_error(Gitlab::Conflict::File::MissingResolution) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) end end @@ -144,7 +145,7 @@ describe Gitlab::Conflict::File do end context 'with an example file' do - let(:file) do + let(:raw_conflict_content) do <<FILE # Ensure there is no match line header here def username_regexp @@ -220,7 +221,6 @@ end FILE end - let(:conflict_file) { described_class.new({ data: file }, conflict, merge_request: merge_request) } let(:sections) { conflict_file.sections } it 'sets the correct match line headers' do diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index d57ffcae8e1..492659a82b0 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::CurrentSettings do it 'falls back to DB if Redis returns an empty value' do expect(ApplicationSetting).to receive(:cached).and_return(nil) - expect(ApplicationSetting).to receive(:last).and_call_original + expect(ApplicationSetting).to receive(:last).and_call_original.twice expect(current_application_settings).to be_a(ApplicationSetting) end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 90aa4f63dd5..596cc435bd9 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -229,7 +229,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end end - describe '#track_rename', redis: true do + describe '#track_rename', :redis do it 'tracks a rename in redis' do key = 'rename:FakeRenameReservedPathMigrationV1:namespace' @@ -246,7 +246,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca end end - describe '#reverts_for_type', redis: true do + describe '#reverts_for_type', :redis do it 'yields for each tracked rename' do subject.track_rename('project', 'old_path', 'new_path') subject.track_rename('project', 'old_path2', 'new_path2') diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb index 32ac0b88a9b..1143182531f 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb @@ -241,7 +241,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, : end end - describe '#revert_renames', redis: true do + describe '#revert_renames', :redis do it 'renames the routes back to the previous values' do project = create(:project, :repository, path: 'a-project', namespace: namespace) subject.rename_namespace(namespace) diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb index 595e06a9748..8922370b0a0 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb @@ -115,7 +115,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :tr end end - describe '#revert_renames', redis: true do + describe '#revert_renames', :redis do it 'renames the routes back to the previous values' do subject.rename_project(project) diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 5fa94999d25..7aeb85b8f5a 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -256,4 +256,26 @@ describe Gitlab::Database do expect(described_class.false_value).to eq 0 end end + + describe '#sanitize_timestamp' do + let(:max_timestamp) { Time.at((1 << 31) - 1) } + + subject { described_class.sanitize_timestamp(timestamp) } + + context 'with a timestamp smaller than MAX_TIMESTAMP_VALUE' do + let(:timestamp) { max_timestamp - 10.years } + + it 'returns the given timestamp' do + expect(subject).to eq(timestamp) + end + end + + context 'with a timestamp larger than MAX_TIMESTAMP_VALUE' do + let(:timestamp) { max_timestamp + 1.second } + + it 'returns MAX_TIMESTAMP_VALUE' do + expect(subject).to eq(max_timestamp) + end + end + end end diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb index c73708d90a8..f9bfb4c469e 100644 --- a/spec/lib/gitlab/diff/diff_refs_spec.rb +++ b/spec/lib/gitlab/diff/diff_refs_spec.rb @@ -3,6 +3,61 @@ require 'spec_helper' describe Gitlab::Diff::DiffRefs do let(:project) { create(:project, :repository) } + describe '#==' do + let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') } + subject { commit.diff_refs } + + context 'when shas are missing' do + let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: nil) } + + it 'returns false' do + expect(subject).not_to eq(other) + end + end + + context 'when shas are equal' do + let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: subject.head_sha) } + + it 'returns true' do + expect(subject).to eq(other) + end + end + + context 'when shas are unequal' do + let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: subject.head_sha.reverse) } + + it 'returns false' do + expect(subject).not_to eq(other) + end + end + + context 'when shas are truncated' do + context 'when sha prefixes are too short' do + let(:other) { described_class.new(base_sha: subject.base_sha[0, 4], start_sha: subject.start_sha[0, 4], head_sha: subject.head_sha[0, 4]) } + + it 'returns false' do + expect(subject).not_to eq(other) + end + end + + context 'when sha prefixes are equal' do + let(:other) { described_class.new(base_sha: subject.base_sha[0, 10], start_sha: subject.start_sha[0, 10], head_sha: subject.head_sha[0, 10]) } + + it 'returns true' do + expect(subject).to eq(other) + end + end + + context 'when sha prefixes are unequal' do + let(:other) { described_class.new(base_sha: subject.base_sha[0, 10], start_sha: subject.start_sha[0, 10], head_sha: subject.head_sha[0, 10].reverse) } + + it 'returns false' do + expect(subject).not_to eq(other) + end + end + end + end + describe '#compare_in' do context 'with diff refs for the initial commit' do let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') } diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb new file mode 100644 index 00000000000..2f99febe04e --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::ImageFormatter do + it_behaves_like "position formatter" do + let(:base_attrs) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_image.png', + new_path: 'new_image.png', + position_type: 'image' + } + end + + let(:attrs) do + base_attrs.merge(width: 100, height: 100, x: 1, y: 2) + end + end +end diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb new file mode 100644 index 00000000000..897dc917f6a --- /dev/null +++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Gitlab::Diff::Formatters::TextFormatter do + let!(:base) do + { + base_sha: 123, + start_sha: 456, + head_sha: 789, + old_path: 'old_path.txt', + new_path: 'new_path.txt' + } + end + + let!(:complete) do + base.merge(old_line: 1, new_line: 2) + end + + it_behaves_like "position formatter" do + let(:base_attrs) { base } + + let(:attrs) { complete } + end + + # Specific text formatter examples + let!(:formatter) { described_class.new(attrs) } + + describe '#line_age' do + subject { formatter.line_age } + + context ' when there is only new_line' do + let(:attrs) { base.merge(new_line: 1) } + + it { is_expected.to eq('new') } + end + + context ' when there is only old_line' do + let(:attrs) { base.merge(old_line: 1) } + + it { is_expected.to eq('old') } + end + end +end diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index 8af49ed50ff..80c8c189665 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -143,4 +143,21 @@ eos it { expect(parser.parse([])).to eq([]) } it { expect(parser.parse(nil)).to eq([]) } end + + describe 'tolerates special diff markers in a content' do + it "counts lines correctly" do + diff = <<~END + --- a/test + +++ b/test + @@ -1,2 +1,2 @@ + +ipsum + +++ b + -ipsum + END + + lines = parser.parse(diff.lines).to_a + + expect(lines.size).to eq(3) + end + end end diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index d4a2a852c12..677eb373d22 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::Position do let(:project) { create(:project, :repository) } - describe "position for an added file" do + describe "position for an added text file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } subject do @@ -40,13 +40,38 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end end end + describe "position for an added image file" do + let(:commit) { project.commit("33f3729a45c02fc67d00adb1b8bca394b0e761d9") } + + subject do + described_class.new( + old_path: "files/images/6049019_460s.jpg", + new_path: "files/images/6049019_460s.jpg", + width: 100, + height: 100, + x: 1, + y: 100, + diff_refs: commit.diff_refs, + position_type: "image" + ) + end + + it "returns the correct diff file" do + diff_file = subject.diff_file(project.repository) + + expect(diff_file.new_file?).to be true + expect(diff_file.new_path).to eq(subject.new_path) + expect(diff_file.diff_refs).to eq(subject.diff_refs) + end + end + describe "position for a changed file" do let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } @@ -83,7 +108,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, 15) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -124,7 +149,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -164,7 +189,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, 13, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -208,7 +233,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, 5) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -249,7 +274,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -289,7 +314,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, 4, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -332,13 +357,50 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, 0, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end end end + describe "position for a missing ref" do + let(:diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: "not_existing_sha", + head_sha: "existing_sha" + ) + end + + subject do + described_class.new( + old_path: "files/ruby/feature.rb", + new_path: "files/ruby/feature.rb", + old_line: 3, + new_line: nil, + diff_refs: diff_refs + ) + end + + describe "#diff_file" do + it "does not raise exception" do + expect { subject.diff_file(project.repository) }.not_to raise_error + end + end + + describe "#diff_line" do + it "does not raise exception" do + expect { subject.diff_line(project.repository) }.not_to raise_error + end + end + + describe "#line_code" do + it "does not raise exception" do + expect { subject.line_code(project.repository) }.not_to raise_error + end + end + end + describe "position for a file in the initial commit" do let(:commit) { project.commit("1a0b36b3cdad1d2ee32457c102a8c0b7056fa863") } @@ -374,7 +436,7 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0) + line_code = Gitlab::Git.diff_line_code(subject.file_path, subject.new_line, 0) expect(subject.line_code(project.repository)).to eq(line_code) end @@ -422,34 +484,100 @@ describe Gitlab::Diff::Position do describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line) + line_code = Gitlab::Git.diff_line_code(subject.file_path, 0, subject.old_line) expect(subject.line_code(project.repository)).to eq(line_code) end end end - describe "#to_json" do - let(:hash) do - { + describe '#==' do + let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") } + + subject do + described_class.new( old_path: "files/ruby/popen.rb", new_path: "files/ruby/popen.rb", old_line: nil, new_line: 14, - base_sha: nil, - head_sha: nil, - start_sha: nil - } + diff_refs: commit.diff_refs + ) + end + + context 'when positions are equal' do + let(:other) { described_class.new(subject.to_h) } + + it 'returns true' do + expect(subject).to eq(other) + end + end + + context 'when positions are equal, except for truncated shas' do + let(:other) { described_class.new(subject.to_h.merge(start_sha: subject.start_sha[0, 10])) } + + it 'returns true' do + expect(subject).to eq(other) + end end - let(:diff_position) { described_class.new(hash) } + context 'when positions are unequal' do + let(:other) { described_class.new(subject.to_h.merge(start_sha: subject.start_sha.reverse)) } + + it 'returns false' do + expect(subject).not_to eq(other) + end + end + end + + describe "#to_json" do + shared_examples "diff position json" do + it "returns the position as JSON" do + expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + end + + it "works when nested under another hash" do + expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + end + end + + context "for text positon" do + let(:hash) do + { + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + base_sha: nil, + head_sha: nil, + start_sha: nil, + position_type: "text" + } + end + + let(:diff_position) { described_class.new(hash) } - it "returns the position as JSON" do - expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + it_behaves_like "diff position json" end - it "works when nested under another hash" do - expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + context "for image positon" do + let(:hash) do + { + old_path: "files/any.img", + new_path: "files/any.img", + base_sha: nil, + head_sha: nil, + start_sha: nil, + width: 100, + height: 100, + x: 1, + y: 100, + position_type: "image" + } + end + + let(:diff_position) { described_class.new(hash) } + + it_behaves_like "diff position json" end end end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index 4fa30d8df8b..e5138705443 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -71,6 +71,10 @@ describe Gitlab::Diff::PositionTracer do Gitlab::Diff::DiffRefs.new(base_sha: base_commit.id, head_sha: head_commit.id) end + def text_position_attrs + [:old_line, :new_line] + end + def position(attrs = {}) attrs.reverse_merge!( diff_refs: old_diff_refs @@ -91,7 +95,11 @@ describe Gitlab::Diff::PositionTracer do expect(new_position.diff_refs).to eq(new_diff_refs) attrs.each do |attr, value| - expect(new_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(new_position.formatter.send(attr)).to eq(value) + else + expect(new_position.send(attr)).to eq(value) + end end end end @@ -110,7 +118,11 @@ describe Gitlab::Diff::PositionTracer do expect(change_position.diff_refs).to eq(change_diff_refs) attrs.each do |attr, value| - expect(change_position.send(attr)).to eq(value) + if text_position_attrs.include?(attr) + expect(change_position.formatter.send(attr)).to eq(value) + else + expect(change_position.send(attr)).to eq(value) + end end end end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 8b14b227e65..9151c66afb3 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -6,6 +6,9 @@ describe Gitlab::EncodingHelper do describe '#encode!' do [ + ["nil", nil, nil], + ["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")], + ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad string"], [ 'leaves ascii only string as is', 'ascii only string', @@ -81,6 +84,9 @@ describe Gitlab::EncodingHelper do describe '#encode_utf8' do [ + ["nil", nil, nil], + ["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")], + ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad stringÃ¥"], [ "encodes valid utf8 encoded string to utf8", "λ, λ, λ".encode("UTF-8"), @@ -95,12 +101,18 @@ describe Gitlab::EncodingHelper do "encodes valid ISO-8859-1 encoded string to utf8", "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("ISO-8859-1", "UTF-8"), "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8") + ], + [ + # Test case from https://gitlab.com/gitlab-org/gitlab-ce/issues/39227 + "Equifax branch name", + "refs/heads/Equifax".encode("UTF-8"), + "refs/heads/Equifax".encode("UTF-8") ] ].each do |description, test_string, xpect| it description do - r = ext_class.encode_utf8(test_string.force_encoding('UTF-8')) + r = ext_class.encode_utf8(test_string) expect(r).to eq(xpect) - expect(r.encoding.name).to eq('UTF-8') + expect(r.encoding.name).to eq('UTF-8') if xpect end end diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index 695fd6f8573..8e524f9b05a 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -18,6 +18,10 @@ describe Gitlab::FileDetector do expect(described_class.type_of('README.md')).to eq(:readme) end + it 'returns nil for a README file in a directory' do + expect(described_class.type_of('foo/README.md')).to be_nil + end + it 'returns the type of a changelog file' do %w(CHANGELOG HISTORY CHANGES NEWS).each do |file| expect(described_class.type_of(file)).to eq(:changelog) @@ -52,6 +56,14 @@ describe Gitlab::FileDetector do end end + it 'returns the type of an issue template' do + expect(described_class.type_of('.gitlab/issue_templates/foo.md')).to eq(:issue_template) + end + + it 'returns the type of a merge request template' do + expect(described_class.type_of('.gitlab/merge_request_templates/foo.md')).to eq(:merge_request_template) + end + it 'returns nil for an unknown file' do expect(described_class.type_of('foo.txt')).to be_nil end diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 465c2012b05..793228701cf 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -73,7 +73,7 @@ describe Gitlab::Git::Blame, seed_helper: true do it_behaves_like 'blaming a file' end - context 'when Gitaly blame feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly blame feature is disabled', :skip_gitaly_mock do it_behaves_like 'blaming a file' end end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index f3945e748ab..c04a9688503 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it_behaves_like 'finding blobs' end - context 'when project_raw_show Gitaly feature is disabled', skip_gitaly_mock: true do + context 'when project_raw_show Gitaly feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding blobs' end end @@ -143,6 +143,16 @@ describe Gitlab::Git::Blob, seed_helper: true do expect(blob.loaded_size).to eq(blob_size) end end + + context 'when sha references a tree' do + it 'returns nil' do + tree = Gitlab::Git::Commit.find(repository, 'master').tree + + blob = Gitlab::Git::Blob.raw(repository, tree.oid) + + expect(blob).to be_nil + end + end end describe '.raw' do @@ -150,7 +160,7 @@ describe Gitlab::Git::Blob, seed_helper: true do it_behaves_like 'finding blobs by ID' end - context 'when the blob_raw Gitaly feature is disabled', skip_gitaly_mock: true do + context 'when the blob_raw Gitaly feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding blobs by ID' end end @@ -226,6 +236,51 @@ describe Gitlab::Git::Blob, seed_helper: true do end end + describe '.batch_lfs_pointers' do + let(:tree_object) { Gitlab::Git::Commit.find(repository, 'master').tree } + + let(:non_lfs_blob) do + Gitlab::Git::Blob.find( + repository, + 'master', + 'README.md' + ) + end + + let(:lfs_blob) do + Gitlab::Git::Blob.find( + repository, + '33bcff41c232a11727ac6d660bd4b0c2ba86d63d', + 'files/lfs/image.jpg' + ) + end + + it 'returns a list of Gitlab::Git::Blob' do + blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id]) + + expect(blobs.count).to eq(1) + expect(blobs).to all( be_a(Gitlab::Git::Blob) ) + end + + it 'silently ignores tree objects' do + blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid]) + + expect(blobs).to eq([]) + end + + it 'silently ignores non lfs objects' do + blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id]) + + expect(blobs).to eq([]) + end + + it 'avoids loading large blobs into memory' do + expect(repository).not_to receive(:lookup) + + described_class.batch_lfs_pointers(repository, [non_lfs_blob.id]) + end + end + describe 'encoding' do context 'file with russian text' do let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") } diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 318a7b7a332..708870060e7 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -7,6 +7,38 @@ describe Gitlab::Git::Branch, seed_helper: true do it { is_expected.to be_kind_of Array } + describe '.find' do + subject { described_class.find(repository, branch) } + + before do + allow(repository).to receive(:find_branch).with(branch) + .and_call_original + end + + context 'when finding branch via branch name' do + let(:branch) { 'master' } + + it 'returns a branch object' do + expect(subject).to be_a(described_class) + expect(subject.name).to eq(branch) + + expect(repository).to have_received(:find_branch).with(branch) + end + end + + context 'when the branch is already a branch' do + let(:commit) { repository.commit('master') } + let(:branch) { described_class.new(repository, 'master', commit.sha, commit) } + + it 'returns a branch object' do + expect(subject).to be_a(described_class) + expect(subject).to eq(branch) + + expect(repository).not_to have_received(:find_branch).with(branch) + end + end + end + describe '#size' do subject { super().size } it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index a3dff6d0d4b..9f4e3c49adc 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -65,34 +65,12 @@ describe Gitlab::Git::Commit, seed_helper: true do end describe "Commit info from gitaly commit" do - let(:id) { 'f00' } - let(:parent_ids) { %w(b45 b46) } let(:subject) { "My commit".force_encoding('ASCII-8BIT') } let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } - let(:committer) do - Gitaly::CommitAuthor.new( - name: generate(:name), - email: generate(:email), - date: Google::Protobuf::Timestamp.new(seconds: 123) - ) - end - let(:author) do - Gitaly::CommitAuthor.new( - name: generate(:name), - email: generate(:email), - date: Google::Protobuf::Timestamp.new(seconds: 456) - ) - end - let(:gitaly_commit) do - Gitaly::GitCommit.new( - id: id, - subject: subject, - body: body, - author: author, - committer: committer, - parent_ids: parent_ids - ) - end + let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body) } + let(:id) { gitaly_commit.id } + let(:committer) { gitaly_commit.committer } + let(:author) { gitaly_commit.author } let(:commit) { described_class.new(repository, gitaly_commit) } it { expect(commit.short_id).to eq(id[0..10]) } @@ -104,7 +82,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(commit.author_name).to eq(author.name) } it { expect(commit.committer_name).to eq(committer.name) } it { expect(commit.committer_email).to eq(committer.email) } - it { expect(commit.parent_ids).to eq(parent_ids) } + it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } context 'no body' do let(:body) { "".force_encoding('ASCII-8BIT') } @@ -283,7 +261,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_should_behave_like '.where' end - describe '.where without gitaly', skip_gitaly_mock: true do + describe '.where without gitaly', :skip_gitaly_mock do it_should_behave_like '.where' end @@ -358,7 +336,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_behaves_like 'finding all commits' end - context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly find_all_commits feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding all commits' context 'while applying a sort order based on the `order` option' do @@ -427,7 +405,7 @@ describe Gitlab::Git::Commit, seed_helper: true do it_should_behave_like '#stats' end - describe '#stats with gitaly disabled', skip_gitaly_mock: true do + describe '#stats with gitaly disabled', :skip_gitaly_mock do it_should_behave_like '#stats' end diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb index fce606a2bb5..7b035a381f1 100644 --- a/spec/lib/gitlab/conflict/parser_spec.rb +++ b/spec/lib/gitlab/git/conflict/parser_spec.rb @@ -1,11 +1,9 @@ require 'spec_helper' -describe Gitlab::Conflict::Parser do - let(:parser) { described_class.new } - - describe '#parse' do +describe Gitlab::Git::Conflict::Parser do + describe '.parse' do def parse_text(text) - parser.parse(text, our_path: 'README.md', their_path: 'README.md') + described_class.parse(text, our_path: 'README.md', their_path: 'README.md') end context 'when the file has valid conflicts' do @@ -87,33 +85,37 @@ CONFLICT end let(:lines) do - parser.parse(text, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb') + described_class.parse(text, our_path: 'files/ruby/regex.rb', their_path: 'files/ruby/regex.rb') + end + let(:old_line_numbers) do + lines.select { |line| line[:type] != 'new' }.map { |line| line[:line_old] } end + let(:new_line_numbers) do + lines.select { |line| line[:type] != 'old' }.map { |line| line[:line_new] } + end + let(:line_indexes) { lines.map { |line| line[:line_obj_index] } } it 'sets our lines as new lines' do - expect(lines[8..13]).to all(have_attributes(type: 'new')) - expect(lines[26..27]).to all(have_attributes(type: 'new')) - expect(lines[56..57]).to all(have_attributes(type: 'new')) + expect(lines[8..13]).to all(include(type: 'new')) + expect(lines[26..27]).to all(include(type: 'new')) + expect(lines[56..57]).to all(include(type: 'new')) end it 'sets their lines as old lines' do - expect(lines[14..19]).to all(have_attributes(type: 'old')) - expect(lines[28..29]).to all(have_attributes(type: 'old')) - expect(lines[58..59]).to all(have_attributes(type: 'old')) + expect(lines[14..19]).to all(include(type: 'old')) + expect(lines[28..29]).to all(include(type: 'old')) + expect(lines[58..59]).to all(include(type: 'old')) end it 'sets non-conflicted lines as both' do - expect(lines[0..7]).to all(have_attributes(type: nil)) - expect(lines[20..25]).to all(have_attributes(type: nil)) - expect(lines[30..55]).to all(have_attributes(type: nil)) - expect(lines[60..62]).to all(have_attributes(type: nil)) + expect(lines[0..7]).to all(include(type: nil)) + expect(lines[20..25]).to all(include(type: nil)) + expect(lines[30..55]).to all(include(type: nil)) + expect(lines[60..62]).to all(include(type: nil)) end - it 'sets consecutive line numbers for index, old_pos, and new_pos' do - old_line_numbers = lines.select { |line| line.type != 'new' }.map(&:old_pos) - new_line_numbers = lines.select { |line| line.type != 'old' }.map(&:new_pos) - - expect(lines.map(&:index)).to eq(0.upto(62).to_a) + it 'sets consecutive line numbers for line_obj_index, line_old, and line_new' do + expect(line_indexes).to eq(0.upto(62).to_a) expect(old_line_numbers).to eq(1.upto(53).to_a) expect(new_line_numbers).to eq(1.upto(53).to_a) end @@ -123,12 +125,12 @@ CONFLICT context 'when there is a non-start delimiter first' do it 'raises UnexpectedDelimiter when there is a middle delimiter first' do expect { parse_text('=======') } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'raises UnexpectedDelimiter when there is an end delimiter first' do expect { parse_text('>>>>>>> README.md') } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'does not raise when there is an end delimiter for a different path first' do @@ -143,12 +145,12 @@ CONFLICT it 'raises UnexpectedDelimiter when it is followed by an end delimiter' do expect { parse_text(start_text + '>>>>>>> README.md' + end_text) } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'raises UnexpectedDelimiter when it is followed by another start delimiter' do expect { parse_text(start_text + start_text + end_text) } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'does not raise when it is followed by a start delimiter for a different path' do @@ -163,12 +165,12 @@ CONFLICT it 'raises UnexpectedDelimiter when it is followed by another middle delimiter' do expect { parse_text(start_text + '=======' + end_text) } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'raises UnexpectedDelimiter when it is followed by a start delimiter' do expect { parse_text(start_text + start_text + end_text) } - .to raise_error(Gitlab::Conflict::Parser::UnexpectedDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::UnexpectedDelimiter) end it 'does not raise when it is followed by a start delimiter for another path' do @@ -181,25 +183,25 @@ CONFLICT start_text = "<<<<<<< README.md\n=======\n" expect { parse_text(start_text) } - .to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::MissingEndDelimiter) expect { parse_text(start_text + '>>>>>>> some-other-path.md') } - .to raise_error(Gitlab::Conflict::Parser::MissingEndDelimiter) + .to raise_error(Gitlab::Git::Conflict::Parser::MissingEndDelimiter) end end context 'other file types' do it 'raises UnmergeableFile when lines is blank, indicating a binary file' do expect { parse_text('') } - .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) + .to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile) expect { parse_text(nil) } - .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) + .to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile) end it 'raises UnmergeableFile when the file is over 200 KB' do expect { parse_text('a' * 204801) } - .to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) + .to raise_error(Gitlab::Git::Conflict::Parser::UnmergeableFile) end # All text from Rugged has an encoding of ASCII_8BIT, so force that in @@ -214,7 +216,7 @@ CONFLICT context 'when the file contains non-UTF-8 characters' do it 'raises UnsupportedEncoding' do expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } - .to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding) + .to raise_error(Gitlab::Git::Conflict::Parser::UnsupportedEncoding) end end end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index 3494f0cc98d..ee657101f4c 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -341,8 +341,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do end context 'when diff is quite large will collapse by default' do - let(:iterator) { [{ diff: 'a' * (Gitlab::Git::Diff.collapse_limit + 1) }] } - let(:max_files) { 100 } + let(:iterator) { [{ diff: 'a' * 20480 }] } context 'when no collapse is set' do let(:expanded) { true } diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index d39b33a0c05..4a7b06003fc 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -31,36 +31,6 @@ EOT [".gitmodules"]).patches.first end - describe 'size limit feature toggles' do - context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do - before do - stub_feature_flags(gitlab_git_diff_size_limit_increase: true) - end - - it 'returns 200 KB for size_limit' do - expect(described_class.size_limit).to eq(200.kilobytes) - end - - it 'returns 100 KB for collapse_limit' do - expect(described_class.collapse_limit).to eq(100.kilobytes) - end - end - - context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do - before do - stub_feature_flags(gitlab_git_diff_size_limit_increase: false) - end - - it 'returns 100 KB for size_limit' do - expect(described_class.size_limit).to eq(100.kilobytes) - end - - it 'returns 10 KB for collapse_limit' do - expect(described_class.collapse_limit).to eq(10.kilobytes) - end - end - end - describe '.new' do context 'using a Hash' do context 'with a small diff' do @@ -77,7 +47,7 @@ EOT context 'using a diff that is too large' do it 'prunes the diff' do - diff = described_class.new(diff: 'a' * (described_class.size_limit + 1)) + diff = described_class.new(diff: 'a' * 204800) expect(diff.diff).to be_empty expect(diff).to be_too_large @@ -115,8 +85,8 @@ EOT # The patch total size is 200, with lines between 21 and 54. # This is a quick-and-dirty way to test this. Ideally, a new patch is # added to the test repo with a size that falls between the real limits. - allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(150) - allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(100) + stub_const("#{described_class}::SIZE_LIMIT", 150) + stub_const("#{described_class}::COLLAPSE_LIMIT", 100) end it 'prunes the diff as a large diff instead of as a collapsed diff' do @@ -356,7 +326,7 @@ EOT describe '#collapsed?' do it 'returns true for a diff that is quite large' do - diff = described_class.new({ diff: 'a' * (described_class.collapse_limit + 1) }, expanded: false) + diff = described_class.new({ diff: 'a' * 20480 }, expanded: false) expect(diff).to be_collapsed end diff --git a/spec/lib/gitlab/git/env_spec.rb b/spec/lib/gitlab/git/env_spec.rb index d9df99bfe05..03836d49518 100644 --- a/spec/lib/gitlab/git/env_spec.rb +++ b/spec/lib/gitlab/git/env_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::Env do - describe "#set" do + describe ".set" do context 'with RequestStore.store disabled' do before do allow(RequestStore).to receive(:active?).and_return(false) @@ -34,25 +34,57 @@ describe Gitlab::Git::Env do end end - describe "#all" do + describe ".all" do context 'with RequestStore.store enabled' do before do allow(RequestStore).to receive(:active?).and_return(true) described_class.set( GIT_OBJECT_DIRECTORY: 'foo', - GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar') + GIT_ALTERNATE_OBJECT_DIRECTORIES: ['bar']) end it 'returns an env hash' do expect(described_class.all).to eq({ 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' + 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => ['bar'] }) end end end - describe "#[]" do + describe ".to_env_hash" do + context 'with RequestStore.store enabled' do + using RSpec::Parameterized::TableSyntax + + let(:key) { 'GIT_OBJECT_DIRECTORY' } + subject { described_class.to_env_hash } + + where(:input, :output) do + nil | nil + 'foo' | 'foo' + [] | '' + ['foo'] | 'foo' + %w[foo bar] | 'foo:bar' + end + + with_them do + before do + allow(RequestStore).to receive(:active?).and_return(true) + described_class.set(key.to_sym => input) + end + + it 'puts the right value in the hash' do + if output + expect(subject.fetch(key)).to eq(output) + else + expect(subject.has_key?(key)).to eq(false) + end + end + end + end + end + + describe ".[]" do context 'with RequestStore.store enabled' do before do allow(RequestStore).to receive(:active?).and_return(true) diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index 0ff4f3bd105..2fe1f5603ce 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -14,6 +14,7 @@ describe Gitlab::Git::Hook do let(:repo_path) { repository.path } let(:user) { create(:user) } let(:gl_id) { Gitlab::GlId.gl_id(user) } + let(:gl_username) { user.username } def create_hook(name) FileUtils.mkdir_p(File.join(repo_path, 'hooks')) @@ -42,6 +43,7 @@ describe Gitlab::Git::Hook do let(:env) do { 'GL_ID' => gl_id, + 'GL_USERNAME' => gl_username, 'PWD' => repo_path, 'GL_PROTOCOL' => 'web', 'GL_REPOSITORY' => gl_repository @@ -59,7 +61,7 @@ describe Gitlab::Git::Hook do .with(env, hook_path, chdir: repo_path).and_call_original end - status, errors = hook.trigger(gl_id, blank, blank, ref) + status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) expect(status).to be true expect(errors).to be_blank end @@ -72,7 +74,7 @@ describe Gitlab::Git::Hook do blank = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - status, errors = hook.trigger(gl_id, blank, blank, ref) + status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) expect(status).to be false expect(errors).to eq("error message from the hook<br>error message from the hook line 2<br>") end @@ -86,7 +88,7 @@ describe Gitlab::Git::Hook do blank = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch' - status, errors = hook.trigger(gl_id, blank, blank, ref) + status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref) expect(status).to be true expect(errors).to be_nil end diff --git a/spec/lib/gitlab/git/hooks_service_spec.rb b/spec/lib/gitlab/git/hooks_service_spec.rb index d4d75b66659..3ed3feb4c74 100644 --- a/spec/lib/gitlab/git/hooks_service_spec.rb +++ b/spec/lib/gitlab/git/hooks_service_spec.rb @@ -1,24 +1,26 @@ require 'spec_helper' describe Gitlab::Git::HooksService, seed_helper: true do - let(:user) { Gitlab::Git::User.new('Jane Doe', 'janedoe@example.com', 'user-456') } + let(:gl_id) { 'user-456' } + let(:gl_username) { 'janedoe' } + let(:user) { Gitlab::Git::User.new(gl_username, 'Jane Doe', 'janedoe@example.com', gl_id) } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, 'project-123') } let(:service) { described_class.new } - - before do - @blankrev = Gitlab::Git::BLANK_SHA - @oldrev = SeedRepo::Commit::PARENT_ID - @newrev = SeedRepo::Commit::ID - @ref = 'refs/heads/feature' - end + let(:blankrev) { Gitlab::Git::BLANK_SHA } + let(:oldrev) { SeedRepo::Commit::PARENT_ID } + let(:newrev) { SeedRepo::Commit::ID } + let(:ref) { 'refs/heads/feature' } describe '#execute' do context 'when receive hooks were successful' do - it 'calls post-receive hook' do - hook = double(trigger: [true, nil]) + let(:hook) { double(:hook) } + + it 'calls all three hooks' do expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + expect(hook).to receive(:trigger).with(gl_id, gl_username, blankrev, newrev, ref) + .exactly(3).times.and_return([true, nil]) - service.execute(user, repository, @blankrev, @newrev, @ref) { } + service.execute(user, repository, blankrev, newrev, ref) { } end end @@ -28,7 +30,7 @@ describe Gitlab::Git::HooksService, seed_helper: true do expect(service).not_to receive(:run_hook).with('post-receive') expect do - service.execute(user, repository, @blankrev, @newrev, @ref) + service.execute(user, repository, blankrev, newrev, ref) end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) end end @@ -40,7 +42,7 @@ describe Gitlab::Git::HooksService, seed_helper: true do expect(service).not_to receive(:run_hook).with('post-receive') expect do - service.execute(user, repository, @blankrev, @newrev, @ref) + service.execute(user, repository, blankrev, newrev, ref) end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) end end diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb new file mode 100644 index 00000000000..c9007d7d456 --- /dev/null +++ b/spec/lib/gitlab/git/lfs_changes_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::Git::LfsChanges do + let(:project) { create(:project, :repository) } + let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + let(:blob_object_id) { '0c304a93cb8430108629bbbcaa27db3343299bc0' } + + subject { described_class.new(project.repository, newrev) } + + describe 'new_pointers' do + before do + allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects).and_yield([blob_object_id]) + end + + it 'uses rev-list to find new objects' do + rev_list = double + allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list) + + expect(rev_list).to receive(:new_objects).and_return([]) + + subject.new_pointers + end + + it 'filters new objects to find lfs pointers' do + expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id]) + + subject.new_pointers(object_limit: 1) + end + + it 'limits new_objects using object_limit' do + expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, []) + + subject.new_pointers(object_limit: 0) + end + end + + describe 'all_pointers' do + it 'uses rev-list to find all objects' do + rev_list = double + allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list) + allow(rev_list).to receive(:all_objects).and_yield([blob_object_id]) + + expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id]) + + subject.all_pointers + end + end +end diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb new file mode 100644 index 00000000000..b033ede9062 --- /dev/null +++ b/spec/lib/gitlab/git/popen_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +describe 'Gitlab::Git::Popen' do + let(:path) { Rails.root.join('tmp').to_s } + + let(:klass) do + Class.new(Object) do + include Gitlab::Git::Popen + end + end + + context 'popen' do + context 'zero status' do + let(:result) { klass.new.popen(%w(ls), path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to include('tests') } + end + + context 'non-zero status' do + let(:result) { klass.new.popen(%w(cat NOTHING), path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to eq(1) } + it { expect(output).to include('No such file or directory') } + end + + context 'unsafe string command' do + it 'raises an error when it gets called with a string argument' do + expect { klass.new.popen('ls', path) }.to raise_error(RuntimeError) + end + end + + context 'with custom options' do + let(:vars) { { 'foobar' => 123, 'PWD' => path } } + let(:options) { { chdir: path } } + + it 'calls popen3 with the provided environment variables' do + expect(Open3).to receive(:popen3).with(vars, 'ls', options) + + klass.new.popen(%w(ls), path, { 'foobar' => 123 }) + end + end + + context 'use stdin' do + let(:result) { klass.new.popen(%w[cat], path) { |stdin| stdin.write 'hello' } } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to eq('hello') } + end + + context 'with lazy block' do + it 'yields a lazy io' do + expect_lazy_io = lambda do |io| + expect(io).to be_a Enumerator::Lazy + expect(io.inspect).to include('#<IO:fd') + end + + klass.new.popen(%w[ls], path, lazy_block: expect_lazy_io) + end + + it "doesn't wait for process exit" do + Timeout.timeout(2) do + klass.new.popen(%w[yes], path, lazy_block: ->(io) {}) + end + end + end + end + + context 'popen_with_timeout' do + let(:timeout) { 1.second } + + context 'no timeout' do + context 'zero status' do + let(:result) { klass.new.popen_with_timeout(%w(ls), timeout, path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + it { expect(output).to include('tests') } + end + + context 'non-zero status' do + let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to eq(1) } + it { expect(output).to include('No such file or directory') } + end + + context 'unsafe string command' do + it 'raises an error when it gets called with a string argument' do + expect { klass.new.popen_with_timeout('ls', timeout, path) }.to raise_error(RuntimeError) + end + end + end + + context 'timeout' do + context 'timeout' do + it "raises a Timeout::Error" do + expect { klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) }.to raise_error(Timeout::Error) + end + + it "handles processes that do not shutdown correctly" do + expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) + end + end + + context 'timeout period' do + let(:time_taken) do + begin + start = Time.now + klass.new.popen_with_timeout(%w(sleep 1000), timeout, path) + rescue + Time.now - start + end + end + + it { expect(time_taken).to be >= timeout } + end + + context 'clean up' do + let(:instance) { klass.new } + + it 'kills the child process' do + expect(instance).to receive(:kill_process_group_for_pid).and_wrap_original do |m, *args| + # is the PID, and it should not be running at this point + m.call(*args) + + pid = args.first + begin + Process.getpgid(pid) + raise "The child process should have been killed" + rescue Errno::ESRCH + end + end + + expect { instance.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) + end + end + end + end +end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 4fc26c625a5..1d4d0c300eb 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#rugged" do - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'raises a storage exception when storage is not available' do broken_repo = described_class.new('broken', 'a/path.git', '') @@ -68,31 +68,52 @@ describe Gitlab::Git::Repository, seed_helper: true do expect { broken_repo.rugged }.to raise_error(Gitlab::Git::Repository::NoRepository) end - context 'with no Git env stored' do - before do - expect(Gitlab::Git::Env).to receive(:all).and_return({}) - end + describe 'alternates keyword argument' do + context 'with no Git env stored' do + before do + allow(Gitlab::Git::Env).to receive(:all).and_return({}) + end - it "whitelist some variables and pass them via the alternates keyword argument" do - expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: []) + it "is passed an empty array" do + expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: []) - repository.rugged + repository.rugged + end end - end - context 'with some Git env stored' do - before do - expect(Gitlab::Git::Env).to receive(:all).and_return({ - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar', - 'GIT_OTHER' => 'another_env' - }) + context 'with absolute and relative Git object dir envvars stored' do + before do + allow(Gitlab::Git::Env).to receive(:all).and_return({ + 'GIT_OBJECT_DIRECTORY_RELATIVE' => './objects/foo', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['./objects/bar', './objects/baz'], + 'GIT_OBJECT_DIRECTORY' => 'ignored', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[ignored ignored], + 'GIT_OTHER' => 'another_env' + }) + end + + it "is passed the relative object dir envvars after being converted to absolute ones" do + alternates = %w[foo bar baz].map { |d| File.join(repository.path, './objects', d) } + expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: alternates) + + repository.rugged + end end - it "whitelist some variables and pass them via the alternates keyword argument" do - expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar]) + context 'with only absolute Git object dir envvars stored' do + before do + allow(Gitlab::Git::Env).to receive(:all).and_return({ + 'GIT_OBJECT_DIRECTORY' => 'foo', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => %w[bar baz], + 'GIT_OTHER' => 'another_env' + }) + end + + it "is passed the absolute object dir envvars as is" do + expect(Rugged::Repository).to receive(:new).with(repository.path, alternates: %w[foo bar baz]) - repository.rugged + repository.rugged + end end end end @@ -384,11 +405,45 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly commit_count feature is disabled', :skip_gitaly_mock do it_behaves_like 'simple commit counting' end end + describe '#has_local_branches?' do + shared_examples 'check for local branches' do + it { expect(repository.has_local_branches?).to eq(true) } + + context 'mutable' do + let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } + + after do + ensure_seeds + end + + it 'returns false when there are no branches' do + # Sanity check + expect(repository.has_local_branches?).to eq(true) + + FileUtils.rm_rf(File.join(repository.path, 'packed-refs')) + heads_dir = File.join(repository.path, 'refs/heads') + FileUtils.rm_rf(heads_dir) + FileUtils.mkdir_p(heads_dir) + + expect(repository.has_local_branches?).to eq(false) + end + end + end + + context 'with gitaly' do + it_behaves_like 'check for local branches' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like 'check for local branches' + end + end + describe "#delete_branch" do shared_examples "deleting a branch" do let(:repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } @@ -419,7 +474,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like "deleting a branch" end - context "when Gitaly delete_branch is disabled", skip_gitaly_mock: true do + context "when Gitaly delete_branch is disabled", :skip_gitaly_mock do it_behaves_like "deleting a branch" end end @@ -455,7 +510,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'creating a branch' end - context 'when Gitaly create_branch feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly create_branch feature is disabled', :skip_gitaly_mock do it_behaves_like 'creating a branch' end end @@ -504,10 +559,10 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#remote_delete" do + describe "#remove_remote" do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.remote_delete("expendable") + @repo.remove_remote("expendable") end it "should remove the remote" do @@ -520,14 +575,16 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#remote_add" do + describe "#remote_update" do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.remote_add("new_remote", SeedHelper::GITLAB_GIT_TEST_REPO_URL) + @repo.remote_update("expendable", url: TEST_NORMAL_REPO_PATH) end it "should add the remote" do - expect(@repo.rugged.remotes.each_name.to_a).to include("new_remote") + expect(@repo.rugged.remotes["expendable"].url).to( + eq(TEST_NORMAL_REPO_PATH) + ) end after(:all) do @@ -536,21 +593,58 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#remote_update" do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.remote_update("expendable", url: TEST_NORMAL_REPO_PATH) + describe '#fetch_mirror' do + let(:new_repository) do + Gitlab::Git::Repository.new('default', 'my_project.git', '') end - it "should add the remote" do - expect(@repo.rugged.remotes["expendable"].url).to( - eq(TEST_NORMAL_REPO_PATH) - ) + subject { new_repository.fetch_mirror(repository.path) } + + before do + Gitlab::Shell.new.add_repository('default', 'my_project') end - after(:all) do - FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) - ensure_seeds + after do + Gitlab::Shell.new.remove_repository(TestEnv.repos_path, 'my_project') + end + + it 'fetches a url as a mirror remote' do + subject + + expect(refs(new_repository.path)).to eq(refs(repository.path)) + end + + context 'with keep-around refs' do + let(:sha) { SeedRepo::Commit::ID } + let(:keep_around_ref) { "refs/keep-around/#{sha}" } + let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" } + + before do + repository.rugged.references.create(keep_around_ref, sha, force: true) + repository.rugged.references.create(tmp_ref, sha, force: true) + end + + it 'includes the temporary and keep-around refs' do + subject + + expect(refs(new_repository.path)).to include(keep_around_ref) + expect(refs(new_repository.path)).to include(tmp_ref) + end + end + end + + describe '#remote_tags' do + let(:target_commit_id) { SeedRepo::Commit::ID } + + subject { repository.remote_tags('upstream') } + + it 'gets the remote tags' do + expect(repository).to receive(:list_remote_tags).with('upstream') + .and_return(["#{target_commit_id}\trefs/tags/v0.0.1\n"]) + + expect(subject.first).to be_an_instance_of(Gitlab::Git::Tag) + expect(subject.first.name).to eq('v0.0.1') + expect(subject.first.dereferenced_target.id).to eq(target_commit_id) end end @@ -895,7 +989,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'extended commit counting' end - context 'when Gitaly count_commits feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do it_behaves_like 'extended commit counting' end end @@ -962,7 +1056,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'finding a branch' end - context 'when Gitaly find_branch feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding a branch' it 'should reload Rugged::Repository and return master' do @@ -1080,8 +1174,35 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#merged_branch_names' do + context 'when branch names are passed' do + it 'only returns the names we are asking' do + names = repository.merged_branch_names(%w[merge-test]) + + expect(names).to contain_exactly('merge-test') + end + + it 'does not return unmerged branch names' do + names = repository.merged_branch_names(%w[feature]) + + expect(names).to be_empty + end + end + + context 'when no branch names are specified' do + it 'returns all merged branch names' do + names = repository.merged_branch_names + + expect(names).to include('merge-test') + expect(names).to include('fix-mode') + expect(names).not_to include('feature') + end + end + end + describe "#ls_files" do let(:master_file_paths) { repository.ls_files("master") } + let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") } let(:not_existed_branch) { repository.ls_files("not_existed_branch") } it "read every file paths of master branch" do @@ -1103,6 +1224,10 @@ describe Gitlab::Git::Repository, seed_helper: true do it "returns empty array when not existed branch" do expect(not_existed_branch.length).to equal(0) end + + it "returns valid utf-8 data" do + expect(utf8_file_paths.map { |file| file.force_encoding('utf-8') }).to all(be_valid_encoding) + end end describe "#copy_gitattributes" do @@ -1204,7 +1329,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of refs' end - context 'when Gitaly ref_exists feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of refs' end end @@ -1226,7 +1351,7 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of tags' end - context 'when Gitaly ref_exists_tags feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists_tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of tags' end end @@ -1250,11 +1375,29 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'checks the existence of branches' end - context 'when Gitaly ref_exists_branches feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly ref_exists_branches feature is disabled', :skip_gitaly_mock do it_behaves_like 'checks the existence of branches' end end + describe '#batch_existence' do + let(:refs) { ['deadbeef', SeedRepo::RubyBlob::ID, '909e6157199'] } + + it 'returns existing refs back' do + result = repository.batch_existence(refs) + + expect(result).to eq([SeedRepo::RubyBlob::ID]) + end + + context 'existing: true' do + it 'inverts meaning and returns non-existing refs' do + result = repository.batch_existence(refs, existing: false) + + expect(result).to eq(%w(deadbeef 909e6157199)) + end + end + end + describe '#local_branches' do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') @@ -1327,11 +1470,275 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'languages' - context 'with rugged', skip_gitaly_mock: true do + context 'with rugged', :skip_gitaly_mock do it_behaves_like 'languages' end end + describe '#with_repo_branch_commit' do + context 'when comparing with the same repository' do + let(:start_repository) { repository } + + context 'when the branch exists' do + let(:start_branch_name) { 'master' } + + it 'yields the commit' do + expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } + .to yield_with_args(an_instance_of(Gitlab::Git::Commit)) + end + end + + context 'when the branch does not exist' do + let(:start_branch_name) { 'definitely-not-master' } + + it 'yields nil' do + expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } + .to yield_with_args(nil) + end + end + end + + context 'when comparing with another repository' do + let(:start_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } + + context 'when the branch exists' do + let(:start_branch_name) { 'master' } + + it 'yields the commit' do + expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } + .to yield_with_args(an_instance_of(Gitlab::Git::Commit)) + end + end + + context 'when the branch does not exist' do + let(:start_branch_name) { 'definitely-not-master' } + + it 'yields nil' do + expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) } + .to yield_with_args(nil) + end + end + end + end + + describe '#fetch_source_branch' do + let(:local_ref) { 'refs/merge-requests/1/head' } + + context 'when the branch exists' do + let(:source_branch) { 'master' } + + it 'writes the ref' do + expect(repository).to receive(:write_ref).with(local_ref, /\h{40}/) + + repository.fetch_source_branch(repository, source_branch, local_ref) + end + + it 'returns true' do + expect(repository.fetch_source_branch(repository, source_branch, local_ref)).to eq(true) + end + end + + context 'when the branch does not exist' do + let(:source_branch) { 'definitely-not-master' } + + it 'does not write the ref' do + expect(repository).not_to receive(:write_ref) + + repository.fetch_source_branch(repository, source_branch, local_ref) + end + + it 'returns false' do + expect(repository.fetch_source_branch(repository, source_branch, local_ref)).to eq(false) + end + end + end + + describe '#rm_branch' do + shared_examples "user deleting a branch" do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository.raw } + let(:user) { create(:user) } + let(:branch_name) { "to-be-deleted-soon" } + + before do + project.team << [user, :developer] + repository.create_branch(branch_name) + end + + it "removes the branch from the repo" do + repository.rm_branch(branch_name, user: user) + + expect(repository.rugged.branches[branch_name]).to be_nil + end + end + + context "when Gitaly user_delete_branch is enabled" do + it_behaves_like "user deleting a branch" + end + + context "when Gitaly user_delete_branch is disabled", :skip_gitaly_mock do + it_behaves_like "user deleting a branch" + end + end + + describe '#write_ref' do + context 'validations' do + using RSpec::Parameterized::TableSyntax + + where(:ref_path, :ref) do + 'foo bar' | '123' + 'foobar' | "12\x003" + end + + with_them do + it 'raises ArgumentError' do + expect { repository.write_ref(ref_path, ref) }.to raise_error(ArgumentError) + end + end + end + end + + describe '#fetch' do + let(:git_path) { Gitlab.config.git.bin_path } + let(:remote_name) { 'my_remote' } + + subject { repository.fetch(remote_name) } + + it 'fetches the remote and returns true if the command was successful' do + expect(repository).to receive(:popen) + .with(%W(#{git_path} fetch #{remote_name}), repository.path) + .and_return(['', 0]) + + expect(subject).to be(true) + end + end + + describe '#merge' do + let(:repository) do + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + end + let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } + let(:user) { build(:user) } + let(:target_branch) { 'test-merge-target-branch' } + + before do + repository.create_branch(target_branch, '6d394385cf567f80a8fd85055db1ab4c5295806f') + end + + after do + FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH) + ensure_seeds + end + + shared_examples '#merge' do + it 'can perform a merge' do + merge_commit_id = nil + result = repository.merge(user, source_sha, target_branch, 'Test merge') do |commit_id| + merge_commit_id = commit_id + end + + expect(result.newrev).to eq(merge_commit_id) + expect(result.repo_created).to eq(false) + expect(result.branch_created).to eq(false) + end + end + + context 'with gitaly' do + it_behaves_like '#merge' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#merge' + end + end + + describe '#ff_merge' do + let(:repository) do + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + end + let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } + let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } + let(:user) { build(:user) } + let(:target_branch) { 'test-ff-target-branch' } + + before do + repository.create_branch(target_branch, branch_head) + end + + after do + ensure_seeds + end + + subject { repository.ff_merge(user, source_sha, target_branch) } + + shared_examples '#ff_merge' do + it 'performs a ff_merge' do + expect(subject.newrev).to eq(source_sha) + expect(subject.repo_created).to be(false) + expect(subject.branch_created).to be(false) + + expect(repository.commit(target_branch).id).to eq(source_sha) + end + + context 'with a non-existing target branch' do + subject { repository.ff_merge(user, source_sha, 'this-isnt-real') } + + it 'throws an ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end + end + + context 'with a non-existing source commit' do + let(:source_sha) { 'f001' } + + it 'throws an ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end + end + + context 'when the source sha is not a descendant of the branch head' do + let(:source_sha) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' } + + it "doesn't perform the ff_merge" do + expect { subject }.to raise_error(Gitlab::Git::CommitError) + + expect(repository.commit(target_branch).id).to eq(branch_head) + end + end + end + + context 'with gitaly' do + it "calls Gitaly's OperationService" do + expect_any_instance_of(Gitlab::GitalyClient::OperationService) + .to receive(:user_ff_branch).with(user, source_sha, target_branch) + .and_return(nil) + + subject + end + + it_behaves_like '#ff_merge' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#ff_merge' + end + end + + describe '#fetch' do + let(:git_path) { Gitlab.config.git.bin_path } + let(:remote_name) { 'my_remote' } + + subject { repository.fetch(remote_name) } + + it 'fetches the remote and returns true if the command was successful' do + expect(repository).to receive(:popen) + .with(%W(#{git_path} fetch #{remote_name}), repository.path) + .and_return(['', 0]) + + expect(subject).to be(true) + end + end + def create_remote_branch(repository, remote_name, branch_name, source_branch_name) source_branch = repository.branches.find { |branch| branch.name == source_branch_name } rugged = repository.rugged @@ -1407,4 +1814,10 @@ describe Gitlab::Git::Repository, seed_helper: true do sha = Rugged::Commit.create(repo, options) repo.lookup(sha) end + + def refs(dir) + IO.popen(%W[git -C #{dir} for-each-ref], &:read).split("\n").map do |line| + line.split("\t").last + end + end end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index c0eac98d718..eaf74951b0e 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -2,53 +2,112 @@ require 'spec_helper' describe Gitlab::Git::RevList do let(:project) { create(:project, :repository) } + let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } + let(:env_hash) do + { + 'GIT_OBJECT_DIRECTORY' => 'foo', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' + } + end before do - expect(Gitlab::Git::Env).to receive(:all).and_return({ - GIT_OBJECT_DIRECTORY: 'foo', - GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar' - }) + allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash.symbolize_keys) end - context "#new_refs" do - let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } + def args_for_popen(args_list) + [ + Gitlab.config.git.bin_path, + "--git-dir=#{project.repository.path_to_repo}", + 'rev-list', + *args_list + ] + end - it 'calls out to `popen`' do - expect(rev_list).to receive(:popen).with([ - Gitlab.config.git.bin_path, - "--git-dir=#{project.repository.path_to_repo}", - 'rev-list', - 'newrev', - '--not', - '--all' - ], + def stub_popen_rev_list(*additional_args, output:) + args = args_for_popen(additional_args) + + expect(rev_list).to receive(:popen).with(args, nil, env_hash) + .and_return([output, 0]) + end + + def stub_lazy_popen_rev_list(*additional_args, output:) + params = [ + args_for_popen(additional_args), nil, - { - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' - }).and_return(["sha1\nsha2", 0]) + env_hash, + hash_including(lazy_block: anything) + ] + + expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:| + lazy_block.call(output.split("\n").lazy) + end + end + + context "#new_refs" do + it 'calls out to `popen`' do + stub_popen_rev_list('newrev', '--not', '--all', output: "sha1\nsha2") expect(rev_list.new_refs).to eq(%w[sha1 sha2]) end end + context '#new_objects' do + it 'fetches list of newly pushed objects using rev-list' do + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + + expect(rev_list.new_objects).to eq(%w[sha1 sha2]) + end + + it 'can skip pathless objects' do + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file") + + expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2]) + end + + it 'can yield a lazy enumerator' do + stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + + rev_list.new_objects do |object_ids| + expect(object_ids).to be_a Enumerator::Lazy + end + end + + it 'returns the result of the block when given' do + stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + + objects = rev_list.new_objects do |object_ids| + object_ids.first + end + + expect(objects).to eq 'sha1' + end + + it 'can accept list of references to exclude' do + stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2") + + expect(rev_list.new_objects(not_in: ['master'])).to eq(%w[sha1 sha2]) + end + + it 'handles empty list of references to exclude as listing all known objects' do + stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2") + + expect(rev_list.new_objects(not_in: [])).to eq(%w[sha1 sha2]) + end + end + + context '#all_objects' do + it 'fetches list of all pushed objects using rev-list' do + stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2") + + expect(rev_list.all_objects).to eq(%w[sha1 sha2]) + end + end + context "#missed_ref" do let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } it 'calls out to `popen`' do - expect(rev_list).to receive(:popen).with([ - Gitlab.config.git.bin_path, - "--git-dir=#{project.repository.path_to_repo}", - 'rev-list', - '--max-count=1', - 'oldrev', - '^newrev' - ], - nil, - { - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' - }).and_return(["sha1\nsha2", 0]) + stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', output: "sha1\nsha2") expect(rev_list.missed_ref).to eq(%w[sha1 sha2]) end diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb index 98cf7966dad..72dabca793a 100644 --- a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb +++ b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb @@ -10,18 +10,10 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: # Override test-settings for the circuitbreaker with something more realistic # for these specs. stub_storage_settings('default' => { - 'path' => TestEnv.repos_path, - 'failure_count_threshold' => 10, - 'failure_wait_time' => 30, - 'failure_reset_time' => 1800, - 'storage_timeout' => 5 + 'path' => TestEnv.repos_path }, 'broken' => { - 'path' => 'tmp/tests/non-existent-repositories', - 'failure_count_threshold' => 10, - 'failure_wait_time' => 30, - 'failure_reset_time' => 1800, - 'storage_timeout' => 5 + 'path' => 'tmp/tests/non-existent-repositories' }, 'nopath' => { 'path' => nil } ) @@ -49,6 +41,10 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: expect(key_exists).to be_falsey end + + it 'does not break when there are no keys in redis' do + expect { described_class.reset_all! }.not_to raise_error + end end describe '.for_storage' do @@ -75,19 +71,79 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: expect(circuit_breaker.hostname).to eq(hostname) expect(circuit_breaker.storage).to eq('default') expect(circuit_breaker.storage_path).to eq(TestEnv.repos_path) - expect(circuit_breaker.failure_count_threshold).to eq(10) - expect(circuit_breaker.failure_wait_time).to eq(30) - expect(circuit_breaker.failure_reset_time).to eq(1800) - expect(circuit_breaker.storage_timeout).to eq(5) + end + end + + context 'circuitbreaker settings' do + before do + stub_application_setting(circuitbreaker_failure_count_threshold: 0, + circuitbreaker_failure_wait_time: 1, + circuitbreaker_failure_reset_time: 2, + circuitbreaker_storage_timeout: 3, + circuitbreaker_access_retries: 4, + circuitbreaker_backoff_threshold: 5) + end + + describe '#failure_count_threshold' do + it 'reads the value from settings' do + expect(circuit_breaker.failure_count_threshold).to eq(0) + end + end + + describe '#failure_wait_time' do + it 'reads the value from settings' do + expect(circuit_breaker.failure_wait_time).to eq(1) + end + end + + describe '#failure_reset_time' do + it 'reads the value from settings' do + expect(circuit_breaker.failure_reset_time).to eq(2) + end + end + + describe '#storage_timeout' do + it 'reads the value from settings' do + expect(circuit_breaker.storage_timeout).to eq(3) + end + end + + describe '#access_retries' do + it 'reads the value from settings' do + expect(circuit_breaker.access_retries).to eq(4) + end + end + + describe '#backoff_threshold' do + it 'reads the value from settings' do + expect(circuit_breaker.backoff_threshold).to eq(5) + end end end describe '#perform' do - it 'raises an exception with retry time when the circuit is open' do - allow(circuit_breaker).to receive(:circuit_broken?).and_return(true) + it 'raises the correct exception when the circuit is open' do + set_in_redis(:last_failure, 1.day.ago.to_f) + set_in_redis(:failure_count, 999) expect { |b| circuit_breaker.perform(&b) } - .to raise_error(Gitlab::Git::Storage::CircuitOpen) + .to raise_error do |exception| + expect(exception).to be_kind_of(Gitlab::Git::Storage::CircuitOpen) + expect(exception.retry_after).to eq(1800) + end + end + + it 'raises the correct exception when backing off' do + Timecop.freeze do + set_in_redis(:last_failure, 1.second.ago.to_f) + set_in_redis(:failure_count, 90) + + expect { |b| circuit_breaker.perform(&b) } + .to raise_error do |exception| + expect(exception).to be_kind_of(Gitlab::Git::Storage::Failing) + expect(exception.retry_after).to eq(30) + end + end end it 'yields the block' do @@ -97,6 +153,7 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: it 'checks if the storage is available' do expect(circuit_breaker).to receive(:check_storage_accessible!) + .and_call_original circuit_breaker.perform { 'hello world' } end @@ -112,204 +169,124 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: .to raise_error(Rugged::OSError) end - context 'with the feature disabled' do - it 'returns the block without checking accessibility' do - stub_feature_flags(git_storage_circuit_breaker: false) - - expect(circuit_breaker).not_to receive(:circuit_broken?) + it 'tracks that the storage was accessible' do + set_in_redis(:failure_count, 10) + set_in_redis(:last_failure, Time.now.to_f) - result = circuit_breaker.perform { 'hello' } + circuit_breaker.perform { '' } - expect(result).to eq('hello') - end + expect(value_from_redis(:failure_count).to_i).to eq(0) + expect(value_from_redis(:last_failure)).to be_empty + expect(circuit_breaker.failure_count).to eq(0) + expect(circuit_breaker.last_failure).to be_nil end - end - describe '#circuit_broken?' do - it 'is working when there is no last failure' do - set_in_redis(:last_failure, nil) - set_in_redis(:failure_count, 0) + it 'only performs the accessibility check once' do + expect(Gitlab::Git::Storage::ForkedStorageCheck) + .to receive(:storage_available?).once.and_call_original - expect(circuit_breaker.circuit_broken?).to be_falsey + 2.times { circuit_breaker.perform { '' } } end - it 'is broken when there was a recent failure' do - Timecop.freeze do - set_in_redis(:last_failure, 1.second.ago.to_f) - set_in_redis(:failure_count, 1) - - expect(circuit_breaker.circuit_broken?).to be_truthy - end - end + it 'calls the check with the correct arguments' do + stub_application_setting(circuitbreaker_storage_timeout: 30, + circuitbreaker_access_retries: 3) - it 'is broken when there are too many failures' do - set_in_redis(:last_failure, 1.day.ago.to_f) - set_in_redis(:failure_count, 200) + expect(Gitlab::Git::Storage::ForkedStorageCheck) + .to receive(:storage_available?).with(TestEnv.repos_path, 30, 3) + .and_call_original - expect(circuit_breaker.circuit_broken?).to be_truthy + circuit_breaker.perform { '' } end - context 'the `failure_wait_time` is set to 0' do + context 'with the feature disabled' do before do - stub_storage_settings('default' => { - 'failure_wait_time' => 0, - 'path' => TestEnv.repos_path - }) - end - - it 'is working even when there is a recent failure' do - Timecop.freeze do - set_in_redis(:last_failure, 0.seconds.ago.to_f) - set_in_redis(:failure_count, 1) - - expect(circuit_breaker.circuit_broken?).to be_falsey - end + stub_feature_flags(git_storage_circuit_breaker: false) end - end - end - - describe "storage_available?" do - context 'the storage is available' do - it 'tracks that the storage was accessible an raises the error' do - expect(circuit_breaker).to receive(:track_storage_accessible) - circuit_breaker.storage_available? - end + it 'returns the block without checking accessibility' do + expect(circuit_breaker).not_to receive(:check_storage_accessible!) - it 'only performs the check once' do - expect(Gitlab::Git::Storage::ForkedStorageCheck) - .to receive(:storage_available?).once.and_call_original + result = circuit_breaker.perform { 'hello' } - 2.times { circuit_breaker.storage_available? } + expect(result).to eq('hello') end - end - - context 'storage is not available' do - let(:storage_name) { 'broken' } - it 'tracks that the storage was inaccessible' do - expect(circuit_breaker).to receive(:track_storage_inaccessible) + it 'allows enabling the feature using an ENV var' do + stub_env('GIT_STORAGE_CIRCUIT_BREAKER', 'true') + expect(circuit_breaker).to receive(:check_storage_accessible!) - circuit_breaker.storage_available? - end - end - end - - describe '#check_storage_accessible!' do - it 'raises an exception with retry time when the circuit is open' do - allow(circuit_breaker).to receive(:circuit_broken?).and_return(true) + result = circuit_breaker.perform { 'hello' } - expect { circuit_breaker.check_storage_accessible! } - .to raise_error do |exception| - expect(exception).to be_kind_of(Gitlab::Git::Storage::CircuitOpen) - expect(exception.retry_after).to eq(30) + expect(result).to eq('hello') end end context 'the storage is not available' do let(:storage_name) { 'broken' } - it 'raises an error' do + it 'raises the correct exception' do expect(circuit_breaker).to receive(:track_storage_inaccessible) - expect { circuit_breaker.check_storage_accessible! } + expect { circuit_breaker.perform { '' } } .to raise_error do |exception| expect(exception).to be_kind_of(Gitlab::Git::Storage::Inaccessible) expect(exception.retry_after).to eq(30) end end - end - end - - describe '#track_storage_inaccessible' do - around do |example| - Timecop.freeze { example.run } - end - it 'records the failure time in redis' do - circuit_breaker.track_storage_inaccessible - - failure_time = value_from_redis(:last_failure) - - expect(Time.at(failure_time.to_i)).to be_within(1.second).of(Time.now) - end - - it 'sets the failure time on the breaker without reloading' do - circuit_breaker.track_storage_inaccessible - - expect(circuit_breaker).not_to receive(:get_failure_info) - expect(circuit_breaker.last_failure).to eq(Time.now) - end - - it 'increments the failure count in redis' do - set_in_redis(:failure_count, 10) - - circuit_breaker.track_storage_inaccessible - - expect(value_from_redis(:failure_count).to_i).to be(11) - end - - it 'increments the failure count on the breaker without reloading' do - set_in_redis(:failure_count, 10) - - circuit_breaker.track_storage_inaccessible + it 'tracks that the storage was inaccessible' do + Timecop.freeze do + expect { circuit_breaker.perform { '' } }.to raise_error(Gitlab::Git::Storage::Inaccessible) - expect(circuit_breaker).not_to receive(:get_failure_info) - expect(circuit_breaker.failure_count).to eq(11) + expect(value_from_redis(:failure_count).to_i).to eq(1) + expect(value_from_redis(:last_failure)).not_to be_empty + expect(circuit_breaker.failure_count).to eq(1) + expect(circuit_breaker.last_failure).to be_within(1.second).of(Time.now) + end + end end end - describe '#track_storage_accessible' do - it 'sets the failure count to zero in redis' do - set_in_redis(:failure_count, 10) - - circuit_breaker.track_storage_accessible - - expect(value_from_redis(:failure_count).to_i).to be(0) - end - - it 'sets the failure count to zero on the breaker without reloading' do - set_in_redis(:failure_count, 10) - - circuit_breaker.track_storage_accessible + describe '#circuit_broken?' do + it 'is working when there is no last failure' do + set_in_redis(:last_failure, nil) + set_in_redis(:failure_count, 0) - expect(circuit_breaker).not_to receive(:get_failure_info) - expect(circuit_breaker.failure_count).to eq(0) + expect(circuit_breaker.circuit_broken?).to be_falsey end - it 'removes the last failure time from redis' do - set_in_redis(:last_failure, Time.now.to_i) - - circuit_breaker.track_storage_accessible + it 'is broken when there are too many failures' do + set_in_redis(:last_failure, 1.day.ago.to_f) + set_in_redis(:failure_count, 200) - expect(circuit_breaker).not_to receive(:get_failure_info) - expect(circuit_breaker.last_failure).to be_nil + expect(circuit_breaker.circuit_broken?).to be_truthy end + end - it 'removes the last failure time from the breaker without reloading' do - set_in_redis(:last_failure, Time.now.to_i) - - circuit_breaker.track_storage_accessible + describe '#backing_off?' do + it 'is true when there was a recent failure' do + Timecop.freeze do + set_in_redis(:last_failure, 1.second.ago.to_f) + set_in_redis(:failure_count, 90) - expect(value_from_redis(:last_failure)).to be_empty + expect(circuit_breaker.backing_off?).to be_truthy + end end - it 'wont connect to redis when there are no failures' do - expect(Gitlab::Git::Storage.redis).to receive(:with).once - .and_call_original - expect(circuit_breaker).to receive(:track_storage_accessible) - .and_call_original - - circuit_breaker.track_storage_accessible - end - end + context 'the `failure_wait_time` is set to 0' do + before do + stub_application_setting(circuitbreaker_failure_wait_time: 0) + end - describe '#no_failures?' do - it 'is false when a failure was tracked' do - set_in_redis(:last_failure, Time.now.to_i) - set_in_redis(:failure_count, 1) + it 'is working even when there are failures' do + Timecop.freeze do + set_in_redis(:last_failure, 0.seconds.ago.to_f) + set_in_redis(:failure_count, 90) - expect(circuit_breaker.no_failures?).to be_falsey + expect(circuit_breaker.backing_off?).to be_falsey + end + end end end @@ -329,10 +306,4 @@ describe Gitlab::Git::Storage::CircuitBreaker, clean_gitlab_redis_shared_state: expect(circuit_breaker.failure_count).to eq(7) end end - - describe '#cache_key' do - it 'includes storage and host' do - expect(circuit_breaker.cache_key).to eq(cache_key) - end - end end diff --git a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb index c708b15853a..39a5d020bb4 100644 --- a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb +++ b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb @@ -33,6 +33,21 @@ describe Gitlab::Git::Storage::ForkedStorageCheck, broken_storage: true, skip_da expect(runtime).to be < 1.0 end + it 'will try the specified amount of times before failing' do + allow(described_class).to receive(:check_filesystem_in_process) do + Process.spawn("sleep 10") + end + + expect(Process).to receive(:spawn).with('sleep 10').twice + .and_call_original + + runtime = Benchmark.realtime do + described_class.storage_available?(existing_path, 0.5, 2) + end + + expect(runtime).to be < 1.0 + end + describe 'when using paths with spaces' do let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') } let(:path_with_spaces) { File.join(test_dir, 'path with spaces') } diff --git a/spec/lib/gitlab/git/storage/health_spec.rb b/spec/lib/gitlab/git/storage/health_spec.rb index 2d3af387971..4a14a5201d1 100644 --- a/spec/lib/gitlab/git/storage/health_spec.rb +++ b/spec/lib/gitlab/git/storage/health_spec.rb @@ -20,36 +20,6 @@ describe Gitlab::Git::Storage::Health, clean_gitlab_redis_shared_state: true, br end end - describe '.load_for_keys' do - let(:subject) do - results = Gitlab::Git::Storage.redis.with do |redis| - fake_future = double - allow(fake_future).to receive(:value).and_return([host1_key]) - described_class.load_for_keys({ 'broken' => fake_future }, redis) - end - - # Make sure the `Redis#future is loaded - results.inject({}) do |result, (name, info)| - info.each { |i| i[:failure_count] = i[:failure_count].value.to_i } - - result[name] = info - - result - end - end - - it 'loads when there is no info in redis' do - expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 0 }]) - end - - it 'reads the correct values for a storage from redis' do - set_in_redis(host1_key, 5) - set_in_redis(host2_key, 7) - - expect(subject).to eq('broken' => [{ name: host1_key, failure_count: 5 }]) - end - end - describe '.for_all_storages' do it 'loads health status for all configured storages' do healths = described_class.for_all_storages diff --git a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb index 0e645008c88..5db37f55e03 100644 --- a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb +++ b/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb @@ -54,6 +54,10 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do end describe '#failure_count_threshold' do + before do + stub_application_setting(circuitbreaker_failure_count_threshold: 1) + end + it { expect(breaker.failure_count_threshold).to eq(1) } end @@ -61,17 +65,6 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do ours = described_class.public_instance_methods theirs = Gitlab::Git::Storage::CircuitBreaker.public_instance_methods - # These methods are not part of the public API, but are public to allow the - # CircuitBreaker specs to operate. They should be made private over time. - exceptions = %i[ - cache_key - check_storage_accessible! - no_failures? - storage_available? - track_storage_accessible - track_storage_inaccessible - ] - - expect(theirs - ours).to contain_exactly(*exceptions) + expect(theirs - ours).to be_empty end end diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index cc10679ef1e..6c4f538bf01 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -29,7 +29,7 @@ describe Gitlab::Git::Tag, seed_helper: true do it_behaves_like 'Gitlab::Git::Repository#tags' end - context 'when Gitaly tags feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly tags feature is disabled', :skip_gitaly_mock do it_behaves_like 'Gitlab::Git::Repository#tags' end end diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb index 0ebcecb26c0..eb8db819045 100644 --- a/spec/lib/gitlab/git/user_spec.rb +++ b/spec/lib/gitlab/git/user_spec.rb @@ -1,22 +1,56 @@ require 'spec_helper' describe Gitlab::Git::User do + let(:username) { 'janedo' } let(:name) { 'Jane Doe' } let(:email) { 'janedoe@example.com' } let(:gl_id) { 'user-123' } + let(:user) do + described_class.new(username, name, email, gl_id) + end + + subject { described_class.new(username, name, email, gl_id) } + + describe '.from_gitaly' do + let(:gitaly_user) do + Gitaly::User.new(gl_username: username, name: name, email: email, gl_id: gl_id) + end + + subject { described_class.from_gitaly(gitaly_user) } + + it { expect(subject).to eq(user) } + end - subject { described_class.new(name, email, gl_id) } + describe '.from_gitlab' do + let(:user) { build(:user) } + subject { described_class.from_gitlab(user) } + + it { expect(subject).to eq(described_class.new(user.username, user.name, user.email, 'user-')) } + end describe '#==' do - def eq_other(name, email, gl_id) - eq(described_class.new(name, email, gl_id)) + def eq_other(username, name, email, gl_id) + eq(described_class.new(username, name, email, gl_id)) end - it { expect(subject).to eq_other(name, email, gl_id) } + it { expect(subject).to eq_other(username, name, email, gl_id) } + + it { expect(subject).not_to eq_other(nil, nil, nil, nil) } + it { expect(subject).not_to eq_other(username + 'x', name, email, gl_id) } + it { expect(subject).not_to eq_other(username, name + 'x', email, gl_id) } + it { expect(subject).not_to eq_other(username, name, email + 'x', gl_id) } + it { expect(subject).not_to eq_other(username, name, email, gl_id + 'x') } + end + + describe '#to_gitaly' do + subject { user.to_gitaly } - it { expect(subject).not_to eq_other(nil, nil, nil) } - it { expect(subject).not_to eq_other(name + 'x', email, gl_id) } - it { expect(subject).not_to eq_other(name, email + 'x', gl_id) } - it { expect(subject).not_to eq_other(name, email, gl_id + 'x') } + it 'creates a Gitaly::User with the correct data' do + expect(subject).to be_a(Gitaly::User) + expect(subject.gl_username).to eq(username) + expect(subject.name).to eq(name) + expect(subject.email).to eq(email) + expect(subject.gl_id).to eq(gl_id) + end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 458627ee4de..c9643c5da47 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -165,7 +165,7 @@ describe Gitlab::GitAccess do stub_application_setting(rsa_key_restriction: 4096) end - it 'does not allow keys which are too small', aggregate_failures: true do + it 'does not allow keys which are too small', :aggregate_failures do expect(actor).not_to be_valid expect { pull_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.') expect { push_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.') @@ -177,7 +177,7 @@ describe Gitlab::GitAccess do stub_application_setting(rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE) end - it 'does not allow keys which are too small', aggregate_failures: true do + it 'does not allow keys which are too small', :aggregate_failures do expect(actor).not_to be_valid expect { pull_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/) expect { push_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/) @@ -598,6 +598,19 @@ describe Gitlab::GitAccess do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end + + context "when in a read-only GitLab instance" do + before do + create(:protected_branch, name: 'feature', project: project) + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + # Only check admin; if an admin can't do it, other roles can't either + matrix = permissions_matrix[:admin].dup + matrix.each { |key, _| matrix[key] = false } + + run_permission_checks(admin: matrix) + end end describe 'build authentication abilities' do @@ -632,6 +645,16 @@ describe Gitlab::GitAccess do end end + context 'when the repository is read only' do + let(:project) { create(:project, :repository, :read_only) } + + it 'denies push access' do + project.add_master(user) + + expect { push_access_check }.to raise_unauthorized('The repository is temporarily read-only. Please try again later.') + end + end + describe 'deploy key permissions' do let(:key) { create(:deploy_key, user: user, can_push: can_push) } let(:actor) { key } diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 0376b4ee783..1056074264a 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::GitAccessWiki do let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) } let(:project) { create(:project, :repository) } let(:user) { create(:user) } + let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] } let(:redirected_path) { nil } let(:authentication_abilities) do [ @@ -13,19 +14,27 @@ describe Gitlab::GitAccessWiki do ] end - describe 'push_allowed?' do - before do - create(:protected_branch, name: 'master', project: project) - project.team << [user, :developer] - end + describe '#push_access_check' do + context 'when user can :create_wiki' do + before do + create(:protected_branch, name: 'master', project: project) + project.team << [user, :developer] + end - subject { access.check('git-receive-pack', changes) } + subject { access.check('git-receive-pack', changes) } - it { expect { subject }.not_to raise_error } - end + it { expect { subject }.not_to raise_error } + + context 'when in a read-only GitLab instance' do + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end - def changes - ['6f6d7e7ed 570e7b2ab refs/heads/master'] + it 'does not give access to upload wiki code' do + expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a read-only GitLab instance.") + end + end + end end describe '#access_check_download!' do diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb index e1fa8ae03f8..ba7fb168a3b 100644 --- a/spec/lib/gitlab/git_ref_validator_spec.rb +++ b/spec/lib/gitlab/git_ref_validator_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::GitRefValidator do it { expect(described_class.validate('feature/new')).to be_truthy } it { expect(described_class.validate('implement_@all')).to be_truthy } it { expect(described_class.validate('my_new_feature')).to be_truthy } + it { expect(described_class.validate('my-branch')).to be_truthy } it { expect(described_class.validate('#1')).to be_truthy } it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy } it { expect(described_class.validate('feature/~new/')).to be_falsey } @@ -22,4 +23,8 @@ describe Gitlab::GitRefValidator do it { expect(described_class.validate('refs/remotes/')).to be_falsey } it { expect(described_class.validate('refs/heads/feature')).to be_falsey } it { expect(described_class.validate('refs/remotes/origin')).to be_falsey } + it { expect(described_class.validate('-')).to be_falsey } + it { expect(described_class.validate('-branch')).to be_falsey } + it { expect(described_class.validate('.tag')).to be_falsey } + it { expect(described_class.validate('my branch')).to be_falsey } end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 1ef3e2e3a5d..b2275119a04 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -53,7 +53,7 @@ describe Gitlab::GitalyClient::CommitService do end it 'encodes paths correctly' do - expect { client.diff_from_parent(commit, paths: ['encoding/test.txt', 'encoding/テスト.txt']) }.not_to raise_error + expect { client.diff_from_parent(commit, paths: ['encoding/test.txt', 'encoding/テスト.txt', nil]) }.not_to raise_error end end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb new file mode 100644 index 00000000000..d9ec28ab02e --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::OperationService do + let(:project) { create(:project) } + let(:repository) { project.repository.raw } + let(:client) { described_class.new(repository) } + let(:user) { create(:user) } + let(:gitaly_user) { Gitlab::Git::User.from_gitlab(user).to_gitaly } + + describe '#user_create_branch' do + let(:branch_name) { 'new' } + let(:start_point) { 'master' } + let(:request) do + Gitaly::UserCreateBranchRequest.new( + repository: repository.gitaly_repository, + branch_name: branch_name, + start_point: start_point, + user: gitaly_user + ) + end + let(:gitaly_commit) { build(:gitaly_commit) } + let(:commit_id) { gitaly_commit.id } + let(:gitaly_branch) do + Gitaly::Branch.new(name: branch_name, target_commit: gitaly_commit) + end + let(:response) { Gitaly::UserCreateBranchResponse.new(branch: gitaly_branch) } + let(:commit) { Gitlab::Git::Commit.new(repository, gitaly_commit) } + + subject { client.user_create_branch(branch_name, user, start_point) } + + it 'sends a user_create_branch message and returns a Gitlab::git::Branch' do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_create_branch).with(request, kind_of(Hash)) + .and_return(response) + + expect(subject.name).to eq(branch_name) + expect(subject.dereferenced_target).to eq(commit) + end + + context "when pre_receive_error is present" do + let(:response) do + Gitaly::UserCreateBranchResponse.new(pre_receive_error: "something failed") + end + + it "throws a PreReceive exception" do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_create_branch).with(request, kind_of(Hash)) + .and_return(response) + + expect { subject }.to raise_error( + Gitlab::Git::HooksService::PreReceiveError, "something failed") + end + end + end + + describe '#user_delete_branch' do + let(:branch_name) { 'my-branch' } + let(:request) do + Gitaly::UserDeleteBranchRequest.new( + repository: repository.gitaly_repository, + branch_name: branch_name, + user: gitaly_user + ) + end + let(:response) { Gitaly::UserDeleteBranchResponse.new } + + subject { client.user_delete_branch(branch_name, user) } + + it 'sends a user_delete_branch message' do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_delete_branch).with(request, kind_of(Hash)) + .and_return(response) + + subject + end + + context "when pre_receive_error is present" do + let(:response) do + Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed") + end + + it "throws a PreReceive exception" do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_delete_branch).with(request, kind_of(Hash)) + .and_return(response) + + expect { subject }.to raise_error( + Gitlab::Git::HooksService::PreReceiveError, "something failed") + end + end + end + + describe '#user_ff_branch' do + let(:target_branch) { 'my-branch' } + let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } + let(:request) do + Gitaly::UserFFBranchRequest.new( + repository: repository.gitaly_repository, + branch: target_branch, + commit_id: source_sha, + user: gitaly_user + ) + end + let(:branch_update) do + Gitaly::OperationBranchUpdate.new( + commit_id: source_sha, + repo_created: false, + branch_created: false + ) + end + let(:response) { Gitaly::UserFFBranchResponse.new(branch_update: branch_update) } + + subject { client.user_ff_branch(user, source_sha, target_branch) } + + it 'sends a user_ff_branch message and returns a BranchUpdate object' do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_ff_branch).with(request, kind_of(Hash)) + .and_return(response) + + expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate) + expect(subject.newrev).to eq(source_sha) + expect(subject.repo_created).to be(false) + expect(subject.branch_created).to be(false) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 6f59750b4da..8127b4842b7 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -84,14 +84,14 @@ describe Gitlab::GitalyClient::RefService do end end - describe '#find_ref_name', seed_helper: true do + describe '#find_ref_name', :seed_helper do subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') } it { is_expected.to be_utf8 } it { is_expected.to eq('refs/heads/master') } end - describe '#ref_exists?', seed_helper: true do + describe '#ref_exists?', :seed_helper do it 'finds the master branch ref' do expect(client.ref_exists?('refs/heads/master')).to eq(true) end diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index fd5f984601e..cbc7ce1c1b0 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -73,4 +73,15 @@ describe Gitlab::GitalyClient::RepositoryService do client.apply_gitattributes(revision) end end + + describe '#has_local_branches?' do + it 'sends a has_local_branches message' do + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:has_local_branches) + .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash)) + .and_return(double(value: true)) + + expect(client.has_local_branches?).to be(true) + end + end end diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb new file mode 100644 index 00000000000..d1e0136f8c1 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/util_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::Util do + describe '.repository' do + let(:repository_storage) { 'default' } + let(:relative_path) { 'my/repo.git' } + let(:gl_repository) { 'project-1' } + let(:git_object_directory) { '.git/objects' } + let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] } + + subject do + described_class.repository(repository_storage, relative_path, gl_repository) + end + + it 'creates a Gitaly::Repository with the given data' do + allow(Gitlab::Git::Env).to receive(:[]).with('GIT_OBJECT_DIRECTORY_RELATIVE') + .and_return(git_object_directory) + allow(Gitlab::Git::Env).to receive(:[]).with('GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE') + .and_return(git_alternate_object_directory) + + expect(subject).to be_a(Gitaly::Repository) + expect(subject.storage_name).to eq(repository_storage) + expect(subject.relative_path).to eq(relative_path) + expect(subject.gl_repository).to eq(gl_repository) + expect(subject.git_object_directory).to eq(git_object_directory) + expect(subject.git_alternate_object_directories).to eq(git_alternate_object_directory) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 9a84d6e6a67..a1f4e65b8d4 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -38,6 +38,20 @@ describe Gitlab::GitalyClient, skip_gitaly_mock: true do end end + describe 'encode' do + [ + [nil, ""], + ["", ""], + [" ", " "], + %w(a1 a1), + ["ç¼–ç ", "\xE7\xBC\x96\xE7\xA0\x81".b] + ].each do |input, result| + it "encodes #{input.inspect} to #{result.inspect}" do + expect(described_class.encode(input)).to eq result + end + end + end + describe 'allow_n_plus_1_calls' do context 'when RequestStore is enabled', :request_store do it 'returns the result of the allow_n_plus_1_calls block' do diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb index fcd90fab547..2662cc20b32 100644 --- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::GithubImport::WikiFormatter do describe '#disk_path' do it 'appends .wiki to project path' do - expect(wiki.disk_path).to eq project.disk_path + '.wiki' + expect(wiki.disk_path).to eq project.wiki.disk_path end end diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index b07462e4978..a6c99bc07d4 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -63,6 +63,45 @@ describe Gitlab::Gpg::Commit do it_behaves_like 'returns the cached signature on second call' end + context 'commit signed with a subkey' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User3.emails.first) } + + let!(:gpg_key) do + create :gpg_key, key: GpgHelpers::User3.public_key, user: user + end + + let(:gpg_key_subkey) do + gpg_key.subkeys.find_by(fingerprint: '0522DD29B98F167CD8421752E38FFCAF75ABD92A') + end + + before do + allow(Rugged::Commit).to receive(:extract_signature) + .with(Rugged::Repository, commit_sha) + .and_return( + [ + GpgHelpers::User3.signed_commit_signature, + GpgHelpers::User3.signed_commit_base_data + ] + ) + end + + it 'returns a valid signature' do + expect(described_class.new(commit).signature).to have_attributes( + commit_sha: commit_sha, + project: project, + gpg_key: gpg_key_subkey, + gpg_key_primary_keyid: gpg_key_subkey.keyid, + gpg_key_user_name: GpgHelpers::User3.names.first, + gpg_key_user_email: GpgHelpers::User3.emails.first, + verification_status: 'verified' + ) + end + + it_behaves_like 'returns the cached signature on second call' + end + context 'user email does not match the committer email, but is the same user' do let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first } diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index b9fd4d02156..d6000af0ecd 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -2,17 +2,16 @@ require 'rails_helper' RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do describe '#run' do - let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' } - let!(:project) { create :project, :repository, path: 'sample-project' } + let(:signature) { [GpgHelpers::User1.signed_commit_signature, GpgHelpers::User1.signed_commit_base_data] } + let(:committer_email) { GpgHelpers::User1.emails.first } + let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' } + let!(:project) { create :project, :repository, path: 'sample-project' } let!(:raw_commit) do raw_commit = double( :raw_commit, - signature: [ - GpgHelpers::User1.signed_commit_signature, - GpgHelpers::User1.signed_commit_base_data - ], + signature: signature, sha: commit_sha, - committer_email: GpgHelpers::User1.emails.first + committer_email: committer_email ) allow(raw_commit).to receive :save! @@ -29,12 +28,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do allow(Rugged::Commit).to receive(:extract_signature) .with(Rugged::Repository, commit_sha) - .and_return( - [ - GpgHelpers::User1.signed_commit_signature, - GpgHelpers::User1.signed_commit_base_data - ] - ) + .and_return(signature) end context 'gpg signature did have an associated gpg key which was removed later' do @@ -183,5 +177,34 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do ) end end + + context 'gpg signature did not have an associated gpg subkey' do + let(:signature) { [GpgHelpers::User3.signed_commit_signature, GpgHelpers::User3.signed_commit_base_data] } + let(:committer_email) { GpgHelpers::User3.emails.first } + let!(:user) { create :user, email: GpgHelpers::User3.emails.first } + + let!(:invalid_gpg_signature) do + create :gpg_signature, + project: project, + commit_sha: commit_sha, + gpg_key: nil, + gpg_key_primary_keyid: GpgHelpers::User3.subkey_fingerprints.last[24..-1], + verification_status: 'unknown_key' + end + + it 'updates the signature to being valid when the missing gpg key is added' do + # InvalidGpgSignatureUpdater is called by the after_create hook + gpg_key = create(:gpg_key, key: GpgHelpers::User3.public_key, user: user) + subkey = gpg_key.subkeys.last + + expect(invalid_gpg_signature.reload).to have_attributes( + project: project, + commit_sha: commit_sha, + gpg_key_subkey_id: subkey.id, + gpg_key_primary_keyid: subkey.keyid, + verification_status: 'verified' + ) + end + end end end diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 11a2aea1915..ab9a166db00 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -28,6 +28,23 @@ describe Gitlab::Gpg do end end + describe '.subkeys_from_key' do + it 'returns the subkeys by primary key' do + all_subkeys = described_class.subkeys_from_key(GpgHelpers::User1.public_key) + subkeys = all_subkeys[GpgHelpers::User1.primary_keyid] + + expect(subkeys).to be_present + expect(subkeys.first[:keyid]).to be_present + expect(subkeys.first[:fingerprint]).to be_present + end + + it 'returns an empty array when there are not subkeys' do + all_subkeys = described_class.subkeys_from_key(GpgHelpers::User4.public_key) + + expect(all_subkeys[GpgHelpers::User4.primary_keyid]).to be_empty + end + end + describe '.user_infos_from_key' do it 'returns the names and emails' do user_infos = described_class.user_infos_from_key(GpgHelpers::User1.public_key) diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb index 8dc83a6db7f..30686634af4 100644 --- a/spec/lib/gitlab/group_hierarchy_spec.rb +++ b/spec/lib/gitlab/group_hierarchy_spec.rb @@ -18,6 +18,12 @@ describe Gitlab::GroupHierarchy, :postgresql do expect(relation).to include(parent, child1) end + it 'can find ancestors upto a certain level' do + relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1) + + expect(relation).to contain_exactly(child2) + end + it 'uses ancestors_base #initialize argument' do relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors @@ -55,6 +61,28 @@ describe Gitlab::GroupHierarchy, :postgresql do end end + describe '#descendants' do + it 'includes only the descendants' do + relation = described_class.new(Group.where(id: parent)).descendants + + expect(relation).to contain_exactly(child1, child2) + end + end + + describe '#ancestors' do + it 'includes only the ancestors' do + relation = described_class.new(Group.where(id: child2)).ancestors + + expect(relation).to contain_exactly(child1, parent) + end + + it 'can find ancestors upto a certain level' do + relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1) + + expect(relation).to be_empty + end + end + describe '#all_groups' do let(:relation) do described_class.new(Group.where(id: child1.id)).all_groups diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 73dd236a5c6..4c1ca4349ea 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -44,7 +44,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do describe '#readiness' do subject { described_class.readiness } - context 'storage has a tripped circuitbreaker', broken_storage: true do + context 'storage has a tripped circuitbreaker', :broken_storage do let(:repository_storages) { ['broken'] } let(:storages_paths) do Gitlab.config.repositories.storages diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb new file mode 100644 index 00000000000..30da56bec16 --- /dev/null +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +describe Gitlab::HookData::IssuableBuilder do + set(:user) { create(:user) } + + # This shared example requires a `builder` and `user` variable + shared_examples 'issuable hook data' do |kind| + let(:data) { builder.build(user: user) } + + include_examples 'project hook data' do + let(:project) { builder.issuable.project } + end + include_examples 'deprecated repository hook data' + + context "with a #{kind}" do + it 'contains issuable data' do + expect(data[:object_kind]).to eq(kind) + expect(data[:user]).to eq(user.hook_attrs) + expect(data[:project]).to eq(builder.issuable.project.hook_attrs) + expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:changes]).to eq({}) + expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) + end + + it 'does not contain certain keys' do + expect(data).not_to have_key(:assignees) + expect(data).not_to have_key(:assignee) + end + + describe 'changes are given' do + let(:changes) do + { + cached_markdown_version: %w[foo bar], + description: ['A description', 'A cool description'], + description_html: %w[foo bar], + in_progress_merge_commit_sha: %w[foo bar], + lock_version: %w[foo bar], + merge_jid: %w[foo bar], + title: ['A title', 'Hello World'], + title_html: %w[foo bar], + labels: [ + [{ id: 1, title: 'foo' }], + [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }] + ] + } + end + let(:data) { builder.build(user: user, changes: changes) } + + it 'populates the :changes hash' do + expect(data[:changes]).to match(hash_including({ + title: { previous: 'A title', current: 'Hello World' }, + description: { previous: 'A description', current: 'A cool description' }, + labels: { + previous: [{ id: 1, title: 'foo' }], + current: [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }] + } + })) + end + + it 'does not contain certain keys' do + expect(data[:changes]).not_to have_key('cached_markdown_version') + expect(data[:changes]).not_to have_key('description_html') + expect(data[:changes]).not_to have_key('lock_version') + expect(data[:changes]).not_to have_key('title_html') + expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') + expect(data[:changes]).not_to have_key('merge_jid') + end + end + end + end + + describe '#build' do + it_behaves_like 'issuable hook data', 'issue' do + let(:issuable) { create(:issue, description: 'A description') } + let(:builder) { described_class.new(issuable) } + end + + it_behaves_like 'issuable hook data', 'merge_request' do + let(:issuable) { create(:merge_request, description: 'A description') } + let(:builder) { described_class.new(issuable) } + end + + context 'issue is assigned' do + let(:issue) { create(:issue, assignees: [user]) } + let(:data) { described_class.new(issue).build(user: user) } + + it 'returns correct hook data' do + expect(data[:object_attributes]['assignee_id']).to eq(user.id) + expect(data[:assignees].first).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignee) + end + end + + context 'merge_request is assigned' do + let(:merge_request) { create(:merge_request, assignee: user) } + let(:data) { described_class.new(merge_request).build(user: user) } + + it 'returns correct hook data' do + expect(data[:object_attributes]['assignee_id']).to eq(user.id) + expect(data[:assignee]).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignees) + end + end + end +end diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb new file mode 100644 index 00000000000..6c529cdd051 --- /dev/null +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::HookData::IssueBuilder do + set(:issue) { create(:issue) } + let(:builder) { described_class.new(issue) } + + describe '#build' do + let(:data) { builder.build } + + it 'includes safe attribute' do + %w[ + assignee_id + author_id + branch_name + closed_at + confidential + created_at + deleted_at + description + due_date + id + iid + last_edited_at + last_edited_by_id + milestone_id + moved_to_id + project_id + relative_position + state + time_estimate + title + updated_at + updated_by_id + ].each do |key| + expect(data).to include(key) + end + end + + it 'includes additional attrs' do + expect(data).to include(:total_time_spent) + expect(data).to include(:human_time_estimate) + expect(data).to include(:human_total_time_spent) + expect(data).to include(:assignee_ids) + end + end +end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb new file mode 100644 index 00000000000..92bf87bbad4 --- /dev/null +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' + +describe Gitlab::HookData::MergeRequestBuilder do + set(:merge_request) { create(:merge_request) } + let(:builder) { described_class.new(merge_request) } + + describe '#build' do + let(:data) { builder.build } + + it 'includes safe attribute' do + %w[ + assignee_id + author_id + created_at + deleted_at + description + head_pipeline_id + id + iid + last_edited_at + last_edited_by_id + merge_commit_sha + merge_error + merge_params + merge_status + merge_user_id + merge_when_pipeline_succeeds + milestone_id + ref_fetched + source_branch + source_project_id + state + target_branch + target_project_id + time_estimate + title + updated_at + updated_by_id + ].each do |key| + expect(data).to include(key) + end + end + + %i[source target].each do |key| + describe "#{key} key" do + include_examples 'project hook data', project_key: key do + let(:project) { merge_request.public_send("#{key}_project") } + end + end + end + + it 'includes additional attrs' do + expect(data).to include(:source) + expect(data).to include(:target) + expect(data).to include(:last_commit) + expect(data).to include(:work_in_progress) + expect(data).to include(:total_time_spent) + expect(data).to include(:human_time_estimate) + expect(data).to include(:human_total_time_spent) + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 3fb8edeb701..6c6b9154a0a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -147,6 +147,10 @@ deploy_keys: - user - deploy_keys_projects - projects +cluster: +- project +- user +- service services: - project - service_hook @@ -177,6 +181,7 @@ project: - tag_taggings - tags - chat_services +- cluster - creator - group - namespace @@ -190,6 +195,7 @@ project: - mattermost_slash_commands_service - slack_slash_commands_service - irker_service +- packagist_service - pivotaltracker_service - prometheus_service - hipchat_service @@ -266,6 +272,10 @@ project: - container_repositories - uploads - members_and_requesters +- build_trace_section_names +- root_of_fork_network +- fork_network_member +- fork_network award_emoji: - awardable - user @@ -277,3 +287,6 @@ timelogs: - user push_event_payload: - event +issue_assignees: +- issue +- assignee
\ No newline at end of file diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index c7fbc2bc92f..dd0ce0dae41 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -1,13 +1,15 @@ require 'spec_helper' describe 'forked project import' do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:forked_from_project) { create(:project, :repository) } - let(:fork_link) { create(:forked_project_link, forked_from_project: project_with_repo) } + let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } @@ -16,7 +18,7 @@ describe 'forked project import' do end let!(:merge_request) do - create(:merge_request, source_project: fork_link.forked_to_project, target_project: project_with_repo) + create(:merge_request, source_project: forked_project, target_project: project_with_repo) end let(:saver) do diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index 4d87f27ce05..473ba40fae7 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -1,13 +1,14 @@ require 'spec_helper' describe Gitlab::ImportExport::MergeRequestParser do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } - let(:forked_from_project) { create(:project, :repository) } - let(:fork_link) { create(:forked_project_link, forked_from_project: project) } + let(:forked_project) { fork_project(project) } let!(:merge_request) do - create(:merge_request, source_project: fork_link.forked_to_project, target_project: project) + create(:merge_request, source_project: forked_project, target_project: project) end let(:parsed_merge_request) do diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json new file mode 100644 index 00000000000..82a1fbd2fc5 --- /dev/null +++ b/spec/lib/gitlab/import_export/project.group.json @@ -0,0 +1,188 @@ +{ + "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", + "visibility_level": 10, + "archived": false, + "milestones": [ + { + "id": 1, + "title": "Project milestone", + "project_id": 8, + "description": "Project-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + } + ], + "labels": [ + { + "id": 2, + "title": "project label", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel", + "priorities": [ + { + "id": 1, + "project_id": 5, + "label_id": 1, + "priority": 1, + "created_at": "2016-10-18T09:35:43.338Z", + "updated_at": "2016-10-18T09:35:43.338Z" + } + ] + } + ], + "issues": [ + { + "id": 1, + "title": "Fugiat est minima quae maxime non similique.", + "assignee_id": null, + "project_id": 8, + "author_id": 1, + "created_at": "2017-07-07T18:13:01.138Z", + "updated_at": "2017-08-15T18:37:40.807Z", + "branch_name": null, + "description": "Quam totam fuga numquam in eveniet.", + "state": "opened", + "iid": 1, + "updated_by_id": 1, + "confidential": false, + "deleted_at": null, + "due_date": null, + "moved_to_id": null, + "lock_version": null, + "time_estimate": 0, + "closed_at": null, + "last_edited_at": null, + "last_edited_by_id": null, + "group_milestone_id": null, + "milestone": { + "id": 1, + "title": "Project milestone", + "project_id": 8, + "description": "Project-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + }, + "label_links": [ + { + "id": 11, + "label_id": 6, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 6, + "title": "group label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "GroupLabel", + "priorities": [] + } + }, + { + "id": 11, + "label_id": 2, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 6, + "title": "project label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "ProjectLabel", + "priorities": [] + } + } + ] + }, + { + "id": 2, + "title": "Fugiat est minima quae maxime non similique.", + "assignee_id": null, + "project_id": 8, + "author_id": 1, + "created_at": "2017-07-07T18:13:01.138Z", + "updated_at": "2017-08-15T18:37:40.807Z", + "branch_name": null, + "description": "Quam totam fuga numquam in eveniet.", + "state": "opened", + "iid": 2, + "updated_by_id": 1, + "confidential": false, + "deleted_at": null, + "due_date": null, + "moved_to_id": null, + "lock_version": null, + "time_estimate": 0, + "closed_at": null, + "last_edited_at": null, + "last_edited_by_id": null, + "group_milestone_id": null, + "milestone": { + "id": 2, + "title": "A group milestone", + "description": "Group-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": 100 + }, + "label_links": [ + { + "id": 11, + "label_id": 2, + "target_id": 1, + "target_type": "Issue", + "created_at": "2017-08-15T18:37:40.795Z", + "updated_at": "2017-08-15T18:37:40.795Z", + "label": { + "id": 2, + "title": "project label", + "color": "#A8D695", + "project_id": null, + "created_at": "2017-08-15T18:37:19.698Z", + "updated_at": "2017-08-15T18:37:19.698Z", + "template": false, + "description": "", + "group_id": 5, + "type": "ProjectLabel", + "priorities": [] + } + } + ] + } + ], + "snippets": [ + + ], + "hooks": [ + + ] +} diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1115fb218d6..9a68bbb379c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -43,7 +43,7 @@ "issues": [ { "id": 40, - "title": "Voluptatem amet doloribus deleniti eos maxime repudiandae molestias.", + "title": "Voluptatem", "assignee_id": 1, "author_id": 22, "project_id": 5, @@ -60,6 +60,12 @@ "due_date": null, "moved_to_id": null, "test_ee_field": "test", + "issue_assignees": [ + { + "user_id": 1, + "issue_id": 1 + } + ], "milestone": { "id": 1, "title": "test milestone", diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 2d8f3d4a566..02450478a77 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -5,9 +5,9 @@ "milestones": [ { "id": 1, - "title": "test milestone", + "title": "Project milestone", "project_id": 8, - "description": "test milestone", + "description": "Project-level milestone", "due_date": null, "created_at": "2016-06-14T15:02:04.415Z", "updated_at": "2016-06-14T15:02:04.415Z", @@ -19,7 +19,7 @@ "labels": [ { "id": 2, - "title": "test2", + "title": "A project label", "color": "#428bca", "project_id": 8, "created_at": "2016-07-22T08:55:44.161Z", @@ -63,30 +63,21 @@ "last_edited_at": null, "last_edited_by_id": null, "group_milestone_id": null, + "milestone": { + "id": 1, + "title": "Project milestone", + "project_id": 8, + "description": "Project-level milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "group_id": null + }, "label_links": [ { "id": 11, - "label_id": 6, - "target_id": 1, - "target_type": "Issue", - "created_at": "2017-08-15T18:37:40.795Z", - "updated_at": "2017-08-15T18:37:40.795Z", - "label": { - "id": 6, - "title": "group label", - "color": "#A8D695", - "project_id": null, - "created_at": "2017-08-15T18:37:19.698Z", - "updated_at": "2017-08-15T18:37:19.698Z", - "template": false, - "description": "", - "group_id": 5, - "type": "GroupLabel", - "priorities": [] - } - }, - { - "id": 11, "label_id": 2, "target_id": 1, "target_type": "Issue", @@ -94,14 +85,14 @@ "updated_at": "2017-08-15T18:37:40.795Z", "label": { "id": 6, - "title": "project label", + "title": "Another project label", "color": "#A8D695", "project_id": null, "created_at": "2017-08-15T18:37:19.698Z", "updated_at": "2017-08-15T18:37:19.698Z", "template": false, "description": "", - "group_id": 5, + "group_id": null, "type": "ProjectLabel", "priorities": [] } @@ -109,10 +100,6 @@ ] } ], - "snippets": [ - - ], - "hooks": [ - - ] + "snippets": [], + "hooks": [] } diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index efe11ca794a..76b01b6a1ec 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'JSON' do it 'restores models based on JSON' do - expect(@restored_project_json).to be true + expect(@restored_project_json).to be_truthy end it 'restore correct project features' do @@ -63,6 +63,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') end + it 'has issue assignees' do + expect(Issue.where(title: 'Voluptatem').first.issue_assignees).not_to be_empty + end + it 'contains the merge access levels on a protected branch' do expect(ProtectedBranch.first.merge_access_levels).not_to be_empty end @@ -182,6 +186,53 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end + shared_examples 'restores project successfully' do + it 'correctly restores project' do + expect(shared.errors).to be_empty + expect(restored_project_json).to be_truthy + end + end + + shared_examples 'restores project correctly' do |**results| + it 'has labels' do + expect(project.labels.size).to eq(results.fetch(:labels, 0)) + end + + it 'has label priorities' do + expect(project.labels.first.priorities).not_to be_empty + end + + it 'has milestones' do + expect(project.milestones.size).to eq(results.fetch(:milestones, 0)) + end + + it 'has issues' do + expect(project.issues.size).to eq(results.fetch(:issues, 0)) + end + + it 'has issue with group label and project label' do + labels = project.issues.first.labels + + expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0)) + end + end + + shared_examples 'restores group correctly' do |**results| + it 'has group label' do + expect(project.group.labels.size).to eq(results.fetch(:labels, 0)) + end + + it 'has group milestone' do + expect(project.group.milestones.size).to eq(results.fetch(:milestones, 0)) + end + + it 'has issue with group label' do + labels = project.issues.first.labels + + expect(labels.where(type: "GroupLabel").count).to eq(results.fetch(:first_issue_labels, 0)) + end + end + context 'Light JSON' do let(:user) { create(:user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } @@ -190,33 +241,45 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do let(:restored_project_json) { project_tree_restorer.restore } before do - project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") - allow(shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') end - context 'project.json file access check' do - it 'does not read a symlink' do - Dir.mktmpdir do |tmpdir| - setup_symlink(tmpdir, 'project.json') - allow(shared).to receive(:export_path).and_call_original + context 'with a simple project' do + before do + project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") + + restored_project_json + end + + it_behaves_like 'restores project correctly', + issues: 1, + labels: 1, + milestones: 1, + first_issue_labels: 1 + + context 'project.json file access check' do + it 'does not read a symlink' do + Dir.mktmpdir do |tmpdir| + setup_symlink(tmpdir, 'project.json') + allow(shared).to receive(:export_path).and_call_original - restored_project_json + restored_project_json - expect(shared.errors.first).to be_nil + expect(shared.errors).to be_empty + end end end - end - context 'when there is an existing build with build token' do - it 'restores project json correctly' do - create(:ci_build, token: 'abcd') + context 'when there is an existing build with build token' do + before do + create(:ci_build, token: 'abcd') + end - expect(restored_project_json).to be true + it_behaves_like 'restores project successfully' end end - context 'with group' do + context 'with a project that has a group' do let!(:project) do create(:project, :builds_disabled, @@ -227,43 +290,22 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end before do - project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.light.json") + project_tree_restorer.instance_variable_set(:@path, "spec/lib/gitlab/import_export/project.group.json") restored_project_json end - it 'correctly restores project' do - expect(restored_project_json).to be_truthy - expect(shared.errors).to be_empty - end - - it 'has labels' do - expect(project.labels.count).to eq(2) - end - - it 'creates group label' do - expect(project.group.labels.count).to eq(1) - end - - it 'has label priorities' do - expect(project.labels.first.priorities).not_to be_empty - end - - it 'has milestones' do - expect(project.milestones.count).to eq(1) - end - - it 'has issue' do - expect(project.issues.count).to eq(1) - expect(project.issues.first.labels.count).to eq(2) - end - - it 'has issue with group label and project label' do - labels = project.issues.first.labels + it_behaves_like 'restores project successfully' + it_behaves_like 'restores project correctly', + issues: 2, + labels: 1, + milestones: 1, + first_issue_labels: 1 - expect(labels.where(type: "GroupLabel").count).to eq(1) - expect(labels.where(type: "ProjectLabel").count).to eq(1) - end + it_behaves_like 'restores group correctly', + labels: 1, + milestones: 1, + first_issue_labels: 1 end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index d9b86e1bf34..8da768ebd07 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -77,6 +77,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(saved_project_json['issues'].first['notes']).not_to be_empty end + it 'has issue assignees' do + expect(saved_project_json['issues'].first['issue_assignees']).not_to be_empty + end + it 'has author on issue comments' do expect(saved_project_json['issues'].first['notes'].first['author']).not_to be_empty end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 899d17d97c2..89d30407077 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -25,6 +25,7 @@ Issue: - relative_position - last_edited_at - last_edited_by_id +- discussion_locked Event: - id - target_type @@ -168,6 +169,7 @@ MergeRequest: - last_edited_at - last_edited_by_id - head_pipeline_id +- discussion_locked MergeRequestDiff: - id - state @@ -224,6 +226,7 @@ Ci::Pipeline: - auto_canceled_by_id - pipeline_schedule_id - config_source +- failure_reason - protected Ci::Stage: - id @@ -310,6 +313,32 @@ Ci::PipelineSchedule: - deleted_at - created_at - updated_at +Gcp::Cluster: +- id +- project_id +- user_id +- service_id +- enabled +- status +- status_reason +- project_namespace +- endpoint +- ca_cert +- encrypted_kubernetes_token +- encrypted_kubernetes_token_iv +- username +- encrypted_password +- encrypted_password_iv +- gcp_project_id +- gcp_cluster_zone +- gcp_cluster_name +- gcp_cluster_size +- gcp_machine_type +- gcp_operation_id +- encrypted_gcp_token +- encrypted_gcp_token_iv +- created_at +- updated_at DeployKey: - id - user_id @@ -412,6 +441,8 @@ Project: - last_repository_updated_at - ci_config_path - delete_error +- merge_requests_ff_only_enabled +- merge_requests_rebase_enabled Author: - name ProjectFeature: @@ -465,6 +496,7 @@ Timelog: - merge_request_id - issue_id - user_id +- spent_at - created_at - updated_at ProjectAutoDevops: @@ -474,3 +506,6 @@ ProjectAutoDevops: - project_id - created_at - updated_at +IssueAssignee: +- user_id +- issue_id
\ No newline at end of file diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb index 8370adf9211..1785094af10 100644 --- a/spec/lib/gitlab/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::LDAP::AuthHash do let(:auth_hash) do described_class.new( OmniAuth::AuthHash.new( - uid: '123456', + uid: given_uid, provider: 'ldapmain', info: info, extra: { @@ -32,6 +32,8 @@ describe Gitlab::LDAP::AuthHash do end context "without overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + it "has the correct username" do expect(auth_hash.username).to eq("123456") end @@ -42,6 +44,8 @@ describe Gitlab::LDAP::AuthHash do end context "with overridden attributes" do + let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' } + let(:attributes) do { 'username' => %w(mail email), @@ -61,4 +65,22 @@ describe Gitlab::LDAP::AuthHash do expect(auth_hash.name).to eq("John Smith") end end + + describe '#uid' do + context 'when there is extraneous (but valid) whitespace' do + let(:given_uid) { 'uid =john smith , ou = people, dc= example,dc =com' } + + it 'removes the extraneous whitespace' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + + context 'when there are upper case characters' do + let(:given_uid) { 'UID=John Smith,ou=People,dc=example,dc=com' } + + it 'downcases' do + expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com') + end + end + end end diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb index 01b6282af0c..9d57a46c12b 100644 --- a/spec/lib/gitlab/ldap/authentication_spec.rb +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Gitlab::LDAP::Authentication do - let(:user) { create(:omniauth_user, extern_uid: dn) } - let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } + let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' } + let(:user) { create(:omniauth_user, extern_uid: Gitlab::LDAP::Person.normalize_dn(dn)) } let(:login) { 'john' } let(:password) { 'password' } diff --git a/spec/lib/gitlab/ldap/dn_spec.rb b/spec/lib/gitlab/ldap/dn_spec.rb new file mode 100644 index 00000000000..8e21ecdf9ab --- /dev/null +++ b/spec/lib/gitlab/ldap/dn_spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +describe Gitlab::LDAP::DN do + using RSpec::Parameterized::TableSyntax + + describe '#normalize_value' do + subject { described_class.normalize_value(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { '"Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { '"James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'foo\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + end + end + + describe '#to_normalized_s' do + subject { described_class.new(given).to_normalized_s } + + it_behaves_like 'normalizes a DN' + + context 'when we do not support the given DN format' do + context 'multivalued RDNs' do + context 'without extraneous whitespace' do + let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + + context 'with extraneous whitespace' do + context 'around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + + context 'not around the phone number plus sign' do + let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' } + + it 'raises UnsupportedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError) + end + end + end + end + end + + context 'when the given DN is malformed' do + context 'when ending with a comma' do + let(:given) { 'uid=John Smith,' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'when given a BER encoded attribute value with a space in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"") + end + end + + context 'when given a BER encoded attribute value with a non-hex character in it' do + let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"") + end + end + + context 'when given a hex pair with a non-hex character in it, inside double quotes' do + let(:given) { 'uid="Sebasti\\cX\\a1n"' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"") + end + end + + context 'without a name value pair' do + let(:given) { 'John' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an open (as opposed to closed) double quote' do + let(:given) { 'cn="James' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid escaped hex code' do + let(:given) { 'cn=J\ames' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"') + end + end + + context 'with a value ending with the escape character' do + let(:given) { 'cn=\\' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly') + end + end + + context 'with an invalid OID attribute type name' do + let(:given) { '1.2.d=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"') + end + end + + context 'with a period in a non-OID attribute type name' do + let(:given) { 'd1.2=Value' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."') + end + end + + context 'when starting with non-space, non-alphanumeric character' do + let(:given) { ' -uid=John Smith' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"') + end + end + + context 'when given a UID with an escaped equal sign' do + let(:given) { 'uid\\=john' } + + it 'raises MalformedError' do + expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"') + end + end + end + end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: \"#{expected}\"\n got: \"#{got}\"" + expect(got).to eq(expected), test_failure_message + end +end diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb index 087c4d8c92c..d204050ef66 100644 --- a/spec/lib/gitlab/ldap/person_spec.rb +++ b/spec/lib/gitlab/ldap/person_spec.rb @@ -16,6 +16,34 @@ describe Gitlab::LDAP::Person do ) end + describe '.normalize_dn' do + subject { described_class.normalize_dn(given) } + + it_behaves_like 'normalizes a DN' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given DN unmodified' do + expect(subject).to eq(given) + end + end + end + + describe '.normalize_uid' do + subject { described_class.normalize_uid(given) } + + it_behaves_like 'normalizes a DN attribute value' + + context 'with an exception during normalization' do + let(:given) { 'John "Smith,' } # just something that will cause an exception + + it 'returns the given UID unmodified' do + expect(subject).to eq(given) + end + end + end + describe '#name' do it 'uses the configured name attribute and handles values as an array' do name = 'John Doe' @@ -43,4 +71,9 @@ describe Gitlab::LDAP::Person do expect(person.email).to eq([user_principal_name]) end end + + def assert_generic_test(test_description, got, expected) + test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}" + expect(got).to eq(expected), test_failure_message + end end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 6a6e465cea2..260df6e4dae 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::LDAP::User do } end let(:auth_hash) do - OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info) + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info) end let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) } let(:info_upper_case) do @@ -22,12 +22,12 @@ describe Gitlab::LDAP::User do } end let(:auth_hash_upper_case) do - OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case) + OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info_upper_case) end describe '#changed?' do it "marks existing ldap user as changed" do - create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain') expect(ldap_user.changed?).to be_truthy end @@ -37,30 +37,32 @@ describe Gitlab::LDAP::User do end it "does not mark existing ldap user as changed" do - create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true) expect(ldap_user.changed?).to be_falsey end end describe '.find_by_uid_and_provider' do + let(:dn) { 'CN=John Ã…ström, CN=Users, DC=Example, DC=com' } + it 'retrieves the correct user' do special_info = { name: 'John Ã…ström', email: 'john@example.com', nickname: 'jastrom' } - special_hash = OmniAuth::AuthHash.new(uid: 'CN=John Ã…ström,CN=Users,DC=Example,DC=com', provider: 'ldapmain', info: special_info) + special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info) special_chars_user = described_class.new(special_hash) user = special_chars_user.save - expect(described_class.find_by_uid_and_provider(special_hash.uid, special_hash.provider)).to eq user + expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user end end describe 'find or create' do it "finds the user if already existing" do - create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain') expect { ldap_user.save }.not_to change { User.count } end @@ -70,7 +72,7 @@ describe Gitlab::LDAP::User do expect { ldap_user.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' end @@ -79,7 +81,7 @@ describe Gitlab::LDAP::User do expect { ldap_user.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' expect(existing_user.id).to eql ldap_user.gl_user.id end @@ -89,7 +91,7 @@ describe Gitlab::LDAP::User do expect { ldap_user_upper_case.save }.not_to change { User.count } existing_user.reload - expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com' expect(existing_user.ldap_identity.provider).to eql 'ldapmain' expect(existing_user.id).to eql ldap_user.gl_user.id end diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb index b576d7173f5..0803ce42fac 100644 --- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb @@ -4,35 +4,30 @@ describe Gitlab::Metrics::SidekiqMiddleware do let(:middleware) { described_class.new } let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } } - describe '#call' do - it 'tracks the transaction' do - worker = double(:worker, class: double(:class, name: 'TestWorker')) + def run(worker, message) + expect(Gitlab::Metrics::Transaction).to receive(:new) + .with('TestWorker#perform') + .and_call_original + + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set) + .with(:sidekiq_queue_duration, instance_of(Float)) - expect(Gitlab::Metrics::Transaction).to receive(:new) - .with('TestWorker#perform') - .and_call_original + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) - expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set) - .with(:sidekiq_queue_duration, instance_of(Float)) + middleware.call(worker, message, :test) { nil } + end - expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) + describe '#call' do + it 'tracks the transaction' do + worker = double(:worker, class: double(:class, name: 'TestWorker')) - middleware.call(worker, message, :test) { nil } + run(worker, message) end it 'tracks the transaction (for messages without `enqueued_at`)' do worker = double(:worker, class: double(:class, name: 'TestWorker')) - expect(Gitlab::Metrics::Transaction).to receive(:new) - .with('TestWorker#perform') - .and_call_original - - expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set) - .with(:sidekiq_queue_duration, instance_of(Float)) - - expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish) - - middleware.call(worker, {}, :test) { nil } + run(worker, {}) end it 'tracks any raised exceptions' do @@ -50,5 +45,18 @@ describe Gitlab::Metrics::SidekiqMiddleware do expect { middleware.call(worker, message, :test) } .to raise_error(RuntimeError) end + + it 'tags the metrics accordingly' do + tags = { one: 1, two: 2 } + worker = double(:worker, class: double(:class, name: 'TestWorker')) + allow(worker).to receive(:metrics_tags).and_return(tags) + + tags.each do |tag, value| + expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:add_tag) + .with(tag, value) + end + + run(worker, message) + end end end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index cab662819ac..67121937398 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -17,89 +17,115 @@ describe Gitlab::Middleware::Go do describe 'when go-get=1' do let(:current_user) { nil } - context 'with simple 2-segment project path' do - let!(:project) { create(:project, :private) } + shared_examples 'go-get=1' do |enabled_protocol:| + context 'with simple 2-segment project path' do + let!(:project) { create(:project, :private) } - context 'with subpackages' do - let(:path) { "#{project.full_path}/subpackage" } + context 'with subpackages' do + let(:path) { "#{project.full_path}/subpackage" } - it 'returns the full project path' do - expect_response_with_path(go, project.full_path) - end - end - - context 'without subpackages' do - let(:path) { project.full_path } - - it 'returns the full project path' do - expect_response_with_path(go, project.full_path) + it 'returns the full project path' do + expect_response_with_path(go, enabled_protocol, project.full_path) + end end - end - end - context 'with a nested project path' do - let(:group) { create(:group, :nested) } - let!(:project) { create(:project, :public, namespace: group) } + context 'without subpackages' do + let(:path) { project.full_path } - shared_examples 'a nested project' do - context 'when the project is public' do it 'returns the full project path' do - expect_response_with_path(go, project.full_path) + expect_response_with_path(go, enabled_protocol, project.full_path) end end + end - context 'when the project is private' do - before do - project.update_attribute(:visibility_level, Project::PRIVATE) - end + context 'with a nested project path' do + let(:group) { create(:group, :nested) } + let!(:project) { create(:project, :public, namespace: group) } - context 'with access to the project' do - let(:current_user) { project.creator } + shared_examples 'a nested project' do + context 'when the project is public' do + it 'returns the full project path' do + expect_response_with_path(go, enabled_protocol, project.full_path) + end + end + context 'when the project is private' do before do - project.team.add_master(current_user) + project.update_attribute(:visibility_level, Project::PRIVATE) end - it 'returns the full project path' do - expect_response_with_path(go, project.full_path) + context 'with access to the project' do + let(:current_user) { project.creator } + + before do + project.team.add_master(current_user) + end + + it 'returns the full project path' do + expect_response_with_path(go, enabled_protocol, project.full_path) + end end - end - context 'without access to the project' do - it 'returns the 2-segment group path' do - expect_response_with_path(go, group.full_path) + context 'without access to the project' do + it 'returns the 2-segment group path' do + expect_response_with_path(go, enabled_protocol, group.full_path) + end end end end - end - context 'with subpackages' do - let(:path) { "#{project.full_path}/subpackage" } + context 'with subpackages' do + let(:path) { "#{project.full_path}/subpackage" } - it_behaves_like 'a nested project' - end + it_behaves_like 'a nested project' + end + + context 'with a subpackage that is not a valid project path' do + let(:path) { "#{project.full_path}/---subpackage" } - context 'with a subpackage that is not a valid project path' do - let(:path) { "#{project.full_path}/---subpackage" } + it_behaves_like 'a nested project' + end + + context 'without subpackages' do + let(:path) { project.full_path } - it_behaves_like 'a nested project' + it_behaves_like 'a nested project' + end end - context 'without subpackages' do - let(:path) { project.full_path } + context 'with a bogus path' do + let(:path) { "http:;url=http://www.example.com'http-equiv='refresh'x='?go-get=1" } + + it 'skips go-import generation' do + expect(app).to receive(:call).and_return('no-go') - it_behaves_like 'a nested project' + go + end + end + end + + context 'with SSH disabled' do + before do + stub_application_setting(enabled_git_access_protocol: 'http') end + + include_examples 'go-get=1', enabled_protocol: :http end - context 'with a bogus path' do - let(:path) { "http:;url=http://www.example.com'http-equiv='refresh'x='?go-get=1" } + context 'with HTTP disabled' do + before do + stub_application_setting(enabled_git_access_protocol: 'ssh') + end - it 'skips go-import generation' do - expect(app).to receive(:call).and_return('no-go') + include_examples 'go-get=1', enabled_protocol: :ssh + end - go + context 'with nothing disabled' do + before do + stub_application_setting(enabled_git_access_protocol: nil) end + + include_examples 'go-get=1', enabled_protocol: nil end end @@ -113,10 +139,16 @@ describe Gitlab::Middleware::Go do middleware.call(env) end - def expect_response_with_path(response, path) + def expect_response_with_path(response, protocol, path) + repository_url = case protocol + when :ssh + "ssh://git@#{Gitlab.config.gitlab.host}/#{path}.git" + when :http, nil + "http://#{Gitlab.config.gitlab.host}/#{path}.git" + end expect(response[0]).to eq(200) expect(response[1]['Content-Type']).to eq('text/html') - expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git http://#{Gitlab.config.gitlab.host}/#{path}.git" /></head></html>} + expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}" /></head></html>} expect(response[2].body).to eq([expected_body]) end end diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb new file mode 100644 index 00000000000..86be06ff595 --- /dev/null +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -0,0 +1,168 @@ +require 'spec_helper' + +describe Gitlab::Middleware::ReadOnly do + include Rack::Test::Methods + + RSpec::Matchers.define :be_a_redirect do + match do |response| + response.status == 301 + end + end + + RSpec::Matchers.define :disallow_request do + match do |middleware| + flash = middleware.send(:rack_flash) + flash['alert'] && flash['alert'].include?('You cannot do writing operations') + end + end + + RSpec::Matchers.define :disallow_request_in_json do + match do |response| + json_response = JSON.parse(response.body) + response.body.include?('You cannot do writing operations') && json_response.key?('message') + end + end + + let(:rack_stack) do + rack = Rack::Builder.new do + use ActionDispatch::Session::CacheStore + use ActionDispatch::Flash + use ActionDispatch::ParamsParser + end + + rack.run(subject) + rack.to_app + end + + subject { described_class.new(fake_app) } + + let(:request) { Rack::MockRequest.new(rack_stack) } + + context 'normal requests to a read-only Gitlab instance' do + let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } } + + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'expects PATCH requests to be disallowed' do + response = request.patch('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects PUT requests to be disallowed' do + response = request.put('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects POST requests to be disallowed' do + response = request.post('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects a internal POST request to be allowed after a disallowed request' do + response = request.post('/test_request') + + expect(response).to be_a_redirect + + response = request.post("/api/#{API::API.version}/internal") + + expect(response).not_to be_a_redirect + end + + it 'expects DELETE requests to be disallowed' do + response = request.delete('/test_request') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + it 'expects POST of new file that looks like an LFS batch url to be disallowed' do + response = request.post('/root/gitlab-ce/new/master/app/info/lfs/objects/batch') + + expect(response).to be_a_redirect + expect(subject).to disallow_request + end + + context 'whitelisted requests' do + it 'expects DELETE request to logout to be allowed' do + response = request.delete('/users/sign_out') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects a POST internal request to be allowed' do + response = request.post("/api/#{API::API.version}/internal") + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects a POST LFS request to batch URL to be allowed' do + response = request.post('/root/rouge.git/info/lfs/objects/batch') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects a POST request to git-upload-pack URL to be allowed' do + response = request.post('/root/rouge.git/git-upload-pack') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + + it 'expects requests to sidekiq admin to be allowed' do + response = request.post('/admin/sidekiq') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + + response = request.get('/admin/sidekiq') + + expect(response).not_to be_a_redirect + expect(subject).not_to disallow_request + end + end + end + + context 'json requests to a read-only GitLab instance' do + let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'application/json' }, ['OK']] } } + let(:content_json) { { 'CONTENT_TYPE' => 'application/json' } } + + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it 'expects PATCH requests to be disallowed' do + response = request.patch('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects PUT requests to be disallowed' do + response = request.put('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects POST requests to be disallowed' do + response = request.post('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + + it 'expects DELETE requests to be disallowed' do + response = request.delete('/test_request', content_json) + + expect(response).to disallow_request_in_json + end + end +end diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb new file mode 100644 index 00000000000..68bd4f93159 --- /dev/null +++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::MultiCollectionPaginator do + subject(:paginator) { described_class.new(Project.all.order(:id), Group.all.order(:id), per_page: 3) } + + it 'combines both collections' do + project = create(:project) + group = create(:group) + + expect(paginator.paginate(1)).to eq([project, group]) + end + + it 'includes elements second collection if first collection is empty' do + group = create(:group) + + expect(paginator.paginate(1)).to eq([group]) + end + + context 'with a full first page' do + let!(:all_groups) { create_list(:group, 4) } + let!(:all_projects) { create_list(:project, 4) } + + it 'knows the total count of the collection' do + expect(paginator.total_count).to eq(8) + end + + it 'fills the first page with elements of the first collection' do + expect(paginator.paginate(1)).to eq(all_projects.take(3)) + end + + it 'fils the second page with a mixture of of the first & second collection' do + first_collection_element = all_projects.last + second_collection_elements = all_groups.take(2) + + expected_collection = [first_collection_element] + second_collection_elements + + expect(paginator.paginate(2)).to eq(expected_collection) + end + + it 'fils the last page with elements from the second collection' do + expected_collection = all_groups[-2..-1] + + expect(paginator.paginate(3)).to eq(expected_collection) + end + end +end diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 8aaf320cbf5..c7471a21fda 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::OAuth::User do let(:oauth_user) { described_class.new(auth_hash) } let(:gl_user) { oauth_user.gl_user } let(:uid) { 'my-uid' } + let(:dn) { 'uid=user1,ou=people,dc=example' } let(:provider) { 'my-provider' } let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) } let(:info_hash) do @@ -197,7 +198,7 @@ describe Gitlab::OAuth::User do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } - allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } + allow(ldap_user).to receive(:dn) { dn } end context "and no account for the LDAP user" do @@ -213,7 +214,7 @@ describe Gitlab::OAuth::User do identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array( [ - { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'ldapmain', extern_uid: dn }, { provider: 'twitter', extern_uid: uid } ] ) @@ -221,7 +222,7 @@ describe Gitlab::OAuth::User do end context "and LDAP user has an account already" do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } it "adds the omniauth identity to the LDAP account" do allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) @@ -234,7 +235,7 @@ describe Gitlab::OAuth::User do identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } expect(identities_as_hash).to match_array( [ - { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'ldapmain', extern_uid: dn }, { provider: 'twitter', extern_uid: uid } ] ) @@ -252,7 +253,7 @@ describe Gitlab::OAuth::User do expect(identities_as_hash) .to match_array( [ - { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'ldapmain', extern_uid: dn }, { provider: 'twitter', extern_uid: uid } ] ) @@ -310,8 +311,8 @@ describe Gitlab::OAuth::User do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] } - allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } - allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) + allow(ldap_user).to receive(:dn) { dn } + allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) end context "and no account for the LDAP user" do @@ -341,7 +342,7 @@ describe Gitlab::OAuth::User do end context 'and LDAP user has an account already' do - let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') } context 'dont block on create (LDAP)' do before do diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 2f989397f7e..ee63c9338c5 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -68,14 +68,27 @@ describe Gitlab::PathRegex do message end - let(:all_routes) do + let(:all_non_legacy_routes) do route_set = Rails.application.routes routes_collection = route_set.routes routes_array = routes_collection.routes - routes_array.map { |route| route.path.spec.to_s } + + non_legacy_routes = routes_array.reject do |route| + route.name.to_s =~ /legacy_(\w*)_redirect/ + end + + non_deprecated_redirect_routes = non_legacy_routes.reject do |route| + app = route.app + # `app.app` is either another app, or `self`. We want to find the final app. + app = app.app while app.try(:app) && app.app != app + + app.is_a?(ActionDispatch::Routing::PathRedirect) && app.block.include?('/-/') + end + + non_deprecated_redirect_routes.map { |route| route.path.spec.to_s } end - let(:routes_without_format) { all_routes.map { |path| without_format(path) } } + let(:routes_without_format) { all_non_legacy_routes.map { |path| without_format(path) } } # Routes not starting with `/:` or `/*` # all routes not starting with a param @@ -84,9 +97,9 @@ describe Gitlab::PathRegex do let(:top_level_words) do words = routes_not_starting_in_wildcard.map do |route| route.split('/')[1] - end.compact.uniq + end.compact - words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s) + (words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s)).uniq end let(:ee_top_level_words) do @@ -95,10 +108,11 @@ describe Gitlab::PathRegex do let(:files_in_public) do git = Gitlab.config.git.bin_path - `cd #{Rails.root} && #{git} ls-files public` + tracked = `cd #{Rails.root} && #{git} ls-files public` .split("\n") .map { |entry| entry.gsub('public/', '') } .uniq + tracked + %w(assets uploads) end # All routes that start with a namespaced path, that have 1 or more @@ -212,7 +226,7 @@ describe Gitlab::PathRegex do it 'accepts group routes' do expect(subject).to match('activity/') expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') + expect(subject).to match('labels/') end it 'is not case sensitive' do @@ -245,7 +259,7 @@ describe Gitlab::PathRegex do it 'accepts group routes' do expect(subject).to match('activity/') expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') + expect(subject).to match('labels/') end end @@ -267,7 +281,7 @@ describe Gitlab::PathRegex do it 'accepts group routes' do expect(subject).to match('activity/more/') expect(subject).to match('group_members/more/') - expect(subject).to match('subgroups/more/') + expect(subject).to match('labels/more/') end end end @@ -291,7 +305,7 @@ describe Gitlab::PathRegex do it 'rejects group routes' do expect(subject).not_to match('root/activity/') expect(subject).not_to match('root/group_members/') - expect(subject).not_to match('root/subgroups/') + expect(subject).not_to match('root/labels/') end end @@ -313,7 +327,7 @@ describe Gitlab::PathRegex do it 'rejects group routes' do expect(subject).not_to match('root/activity/more/') expect(subject).not_to match('root/group_members/more/') - expect(subject).not_to match('root/subgroups/more/') + expect(subject).not_to match('root/labels/more/') end end end @@ -348,7 +362,7 @@ describe Gitlab::PathRegex do it 'accepts group routes' do expect(subject).to match('activity/') expect(subject).to match('group_members/') - expect(subject).to match('subgroups/') + expect(subject).to match('labels/') end it 'is not case sensitive' do @@ -381,7 +395,7 @@ describe Gitlab::PathRegex do it 'accepts group routes' do expect(subject).to match('root/activity/') expect(subject).to match('root/group_members/') - expect(subject).to match('root/subgroups/') + expect(subject).to match('root/labels/') end it 'is not case sensitive' do diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 4567f220c11..b145ca36f26 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -14,7 +14,7 @@ describe 'Gitlab::Popen' do end it { expect(@status).to be_zero } - it { expect(@output).to include('cache') } + it { expect(@output).to include('tests') } end context 'non-zero status' do diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index d19bd611919..57b0ef8d1ad 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -4,9 +4,9 @@ describe Gitlab::ProjectTemplate do describe '.all' do it 'returns a all templates' do expected = [ - described_class.new('rails', 'Ruby on Rails'), - described_class.new('spring', 'Spring'), - described_class.new('express', 'NodeJS Express') + described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'), + described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'), + described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express') ] expect(described_class.all).to be_an(Array) @@ -31,7 +31,7 @@ describe Gitlab::ProjectTemplate do end describe 'instance methods' do - subject { described_class.new('phoenix', 'Phoenix Framework') } + subject { described_class.new('phoenix', 'Phoenix Framework', 'Phoenix description', 'link-to-template') } it { is_expected.to respond_to(:logo, :file, :archive_path) } end diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb new file mode 100644 index 00000000000..8b58f0b3725 --- /dev/null +++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe Gitlab::QuickActions::SpendTimeAndDateSeparator do + subject { described_class } + + shared_examples 'arg line with invalid parameters' do + it 'return nil' do + expect(subject.new(invalid_arg).execute).to eq(nil) + end + end + + shared_examples 'arg line with valid parameters' do + it 'return time and date array' do + expect(subject.new(valid_arg).execute).to eq(expected_response) + end + end + + describe '#execute' do + context 'invalid paramenter in arg line' do + context 'empty arg line' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { '' } + end + end + + context 'future date in arg line' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { '10m 6023-02-02' } + end + end + + context 'unparseable date(invalid mixes of delimiters)' do + it_behaves_like 'arg line with invalid parameters' do + let(:invalid_arg) { '10m 2017.02-02' } + end + end + + context 'trash in arg line' do + let(:invalid_arg) { 'dfjkghdskjfghdjskfgdfg' } + + it 'return nil as time value' do + time_date_response = subject.new(invalid_arg).execute + + expect(time_date_response).to be_an_instance_of(Array) + expect(time_date_response.first).to eq(nil) + end + end + end + + context 'only time present in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:valid_arg) { '2m 3m 5m 1h' } + let(:time) { Gitlab::TimeTrackingFormatter.parse(valid_arg) } + let(:date) { DateTime.now.to_date } + let(:expected_response) { [time, date] } + end + end + + context 'simple time with date in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:raw_time) { '10m' } + let(:raw_date) { '2016-02-02' } + let(:valid_arg) { "#{raw_time} #{raw_date}" } + let(:date) { Date.parse(raw_date) } + let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) } + let(:expected_response) { [time, date] } + end + end + + context 'composite time with date in arg line' do + it_behaves_like 'arg line with valid parameters' do + let(:raw_time) { '2m 10m 1h 3d' } + let(:raw_date) { '2016/02/02' } + let(:valid_arg) { "#{raw_time} #{raw_date}" } + let(:date) { Date.parse(raw_date) } + let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) } + let(:expected_response) { [time, date] } + end + end + end +end diff --git a/spec/lib/gitlab/saml/auth_hash_spec.rb b/spec/lib/gitlab/saml/auth_hash_spec.rb new file mode 100644 index 00000000000..a555935aea3 --- /dev/null +++ b/spec/lib/gitlab/saml/auth_hash_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Saml::AuthHash do + include LoginHelpers + + let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers) } } + subject(:saml_auth_hash) { described_class.new(omniauth_auth_hash) } + + let(:info_hash) do + { + name: 'John', + email: 'john@mail.com' + } + end + + let(:omniauth_auth_hash) do + OmniAuth::AuthHash.new(uid: 'my-uid', + provider: 'saml', + info: info_hash, + extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) } ) + end + + before do + stub_saml_group_config(%w(Developers Freelancers Designers)) + end + + describe '#groups' do + it 'returns array of groups' do + expect(saml_auth_hash.groups).to eq(%w(Developers Freelancers)) + end + + context 'raw info hash attributes empty' do + let(:raw_info_attr) { {} } + + it 'returns an empty array' do + expect(saml_auth_hash.groups).to be_a(Array) + end + end + end +end diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb index 19710029224..1765980e977 100644 --- a/spec/lib/gitlab/saml/user_spec.rb +++ b/spec/lib/gitlab/saml/user_spec.rb @@ -1,11 +1,16 @@ require 'spec_helper' describe Gitlab::Saml::User do + include LdapHelpers + include LoginHelpers + let(:saml_user) { described_class.new(auth_hash) } let(:gl_user) { saml_user.gl_user } let(:uid) { 'my-uid' } + let(:dn) { 'uid=user1,ou=people,dc=example' } let(:provider) { 'saml' } - let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new({ 'groups' => %w(Developers Freelancers Designers) }) }) } + let(:raw_info_attr) { { 'groups' => %w(Developers Freelancers Designers) } } + let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new(raw_info_attr) }) } let(:info_hash) do { name: 'John', @@ -15,22 +20,6 @@ describe Gitlab::Saml::User do let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } describe '#save' do - def stub_omniauth_config(messages) - allow(Gitlab.config.omniauth).to receive_messages(messages) - end - - def stub_ldap_config(messages) - allow(Gitlab::LDAP::Config).to receive_messages(messages) - end - - def stub_basic_saml_config - allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) - end - - def stub_saml_group_config(groups) - allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) - end - before do stub_basic_saml_config end @@ -163,13 +152,17 @@ describe Gitlab::Saml::User do end context 'and a corresponding LDAP person' do + let(:adapter) { ldap_adapter('ldapmain') } + before do allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:username) { uid } allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) } - allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' } - allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) - allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user) + allow(ldap_user).to receive(:dn) { dn } + allow(Gitlab::LDAP::Adapter).to receive(:new).and_return(adapter) + allow(Gitlab::LDAP::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user) + allow(Gitlab::LDAP::Person).to receive(:find_by_dn).with(dn, adapter).and_return(ldap_user) + allow(Gitlab::LDAP::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user) end context 'and no account for the LDAP user' do @@ -181,20 +174,86 @@ describe Gitlab::Saml::User do expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to be 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: dn }, { provider: 'saml', extern_uid: uid }]) end end context 'and LDAP user has an account already' do + let(:auth_hash_base_attributes) do + { + uid: uid, + provider: provider, + info: info_hash, + extra: { + raw_info: OneLogin::RubySaml::Attributes.new( + { 'groups' => %w(Developers Freelancers Designers) } + ) + } + } + end + let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes) } + let(:uid_types) { %w(uid dn email) } + before do create(:omniauth_user, email: 'john@mail.com', - extern_uid: 'uid=user1,ou=People,dc=example', + extern_uid: dn, provider: 'ldapmain', username: 'john') end + shared_examples 'find LDAP person' do |uid_type, uid| + let(:auth_hash) { OmniAuth::AuthHash.new(auth_hash_base_attributes.merge(uid: extern_uid)) } + + before do + nil_types = uid_types - [uid_type] + + nil_types.each do |type| + allow(Gitlab::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil) + end + + allow(Gitlab::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user) + end + + it 'adds the omniauth identity to the LDAP account' do + identities = [ + { provider: 'ldapmain', extern_uid: dn }, + { provider: 'saml', extern_uid: extern_uid } + ] + + identities_as_hash = gl_user.identities.map do |id| + { provider: id.provider, extern_uid: id.extern_uid } + end + + saml_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@mail.com' + expect(gl_user.identities.length).to be 2 + expect(identities_as_hash).to match_array(identities) + end + end + + context 'when uid is an uid' do + it_behaves_like 'find LDAP person', 'uid' do + let(:extern_uid) { uid } + end + end + + context 'when uid is a dn' do + it_behaves_like 'find LDAP person', 'dn' do + let(:extern_uid) { dn } + end + end + + context 'when uid is an email' do + it_behaves_like 'find LDAP person', 'email' do + let(:extern_uid) { 'john@mail.com' } + end + end + it 'adds the omniauth identity to the LDAP account' do saml_user.save @@ -203,7 +262,7 @@ describe Gitlab::Saml::User do expect(gl_user.email).to eql 'john@mail.com' expect(gl_user.identities.length).to be 2 identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: dn }, { provider: 'saml', extern_uid: uid }]) end @@ -219,17 +278,21 @@ describe Gitlab::Saml::User do context 'user has SAML user, and wants to add their LDAP identity' do it 'adds the LDAP identity to the existing SAML user' do - create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'saml', username: 'john') - local_hash = OmniAuth::AuthHash.new(uid: 'uid=user1,ou=People,dc=example', provider: provider, info: info_hash) + create(:omniauth_user, email: 'john@mail.com', extern_uid: dn, provider: 'saml', username: 'john') + + allow(Gitlab::LDAP::Person).to receive(:find_by_uid).with(dn, adapter).and_return(ldap_user) + + local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash) local_saml_user = described_class.new(local_hash) + local_saml_user.save local_gl_user = local_saml_user.gl_user expect(local_gl_user).to be_valid expect(local_gl_user.identities.length).to be 2 identities_as_hash = local_gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } - expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, - { provider: 'saml', extern_uid: 'uid=user1,ou=People,dc=example' }]) + expect(identities_as_hash).to match_array([{ provider: 'ldapmain', extern_uid: dn }, + { provider: 'saml', extern_uid: dn }]) end end end @@ -325,4 +388,16 @@ describe Gitlab::Saml::User do end end end + + describe '#find_user' do + context 'raw info hash attributes empty' do + let(:raw_info_attr) { {} } + + it 'does not mark user as external' do + stub_saml_group_config(%w(Freelancers)) + + expect(saml_user.find_user.external).to be_falsy + end + end + end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 4c5efbde69a..e44a7c23452 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Gitlab::SearchResults do + include ProjectForksHelper + let(:user) { create(:user) } let!(:project) { create(:project, name: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') } @@ -42,7 +44,7 @@ describe Gitlab::SearchResults do end it 'includes merge requests from source and target projects' do - forked_project = create(:project, forked_from_project: project) + forked_project = fork_project(project, user) merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') results = described_class.new(user, Project.where(id: forked_project.id), 'foo') diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index c7930378240..2158b2837e2 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -15,10 +15,6 @@ describe Gitlab::Shell do it { is_expected.to respond_to :add_repository } it { is_expected.to respond_to :remove_repository } it { is_expected.to respond_to :fork_repository } - it { is_expected.to respond_to :add_namespace } - it { is_expected.to respond_to :rm_namespace } - it { is_expected.to respond_to :mv_namespace } - it { is_expected.to respond_to :exists? } it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } @@ -48,14 +44,35 @@ describe Gitlab::Shell do end end - describe '#add_key' do - it 'removes trailing garbage' do - allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) - expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( - [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] - ) + describe 'projects commands' do + let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') } + let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') } + let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') } + + before do + allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path) + allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path) + allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800) + end + + describe '#mv_repository' do + it 'executes the command' do + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [projects_path, 'mv-project', 'storage/path', 'project/path.git', 'new/path.git'] + ) + gitlab_shell.mv_repository('storage/path', 'project/path', 'new/path') + end + end + + describe '#add_key' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end end end @@ -105,30 +122,42 @@ describe Gitlab::Shell do end describe '#add_repository' do - it 'creates a repository' do - created_path = File.join(TestEnv.repos_path, 'project', 'path.git') - hooks_path = File.join(created_path, 'hooks') - - begin - result = gitlab_shell.add_repository(TestEnv.repos_path, 'project/path') + shared_examples '#add_repository' do + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repo_name) { 'project/path' } + let(:created_path) { File.join(repository_storage_path, repo_name + '.git') } - repo_stat = File.stat(created_path) rescue nil - hooks_stat = File.lstat(hooks_path) rescue nil - hooks_dir = File.realpath(hooks_path) - ensure + after do FileUtils.rm_rf(created_path) end - expect(result).to be_truthy - expect(repo_stat.mode & 0o777).to eq(0o770) - expect(hooks_stat.symlink?).to be_truthy - expect(hooks_dir).to eq(gitlab_shell_hooks_path) + it 'creates a repository' do + expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_truthy + + expect(File.stat(created_path).mode & 0o777).to eq(0o770) + + hooks_path = File.join(created_path, 'hooks') + expect(File.lstat(hooks_path)).to be_symlink + expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path) + end + + it 'returns false when the command fails' do + FileUtils.mkdir_p(File.dirname(created_path)) + # This file will block the creation of the repo's .git directory. That + # should cause #add_repository to fail. + FileUtils.touch(created_path) + + expect(gitlab_shell.add_repository(repository_storage, repo_name)).to be_falsy + end end - it 'returns false when the command fails' do - expect(FileUtils).to receive(:mkdir_p).and_raise(Errno::EEXIST) + context 'with gitaly' do + it_behaves_like '#add_repository' + end - expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be_falsy + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#add_repository' end end @@ -136,7 +165,7 @@ describe Gitlab::Shell do it 'returns true when the command succeeds' do expect(Gitlab::Popen).to receive(:popen) .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], - nil, popen_vars).and_return([nil, 0]) + nil, popen_vars).and_return([nil, 0]) expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be true end @@ -144,7 +173,7 @@ describe Gitlab::Shell do it 'returns false when the command fails' do expect(Gitlab::Popen).to receive(:popen) .with([projects_path, 'rm-project', 'current/storage', 'project/path.git'], - nil, popen_vars).and_return(["error", 1]) + nil, popen_vars).and_return(["error", 1]) expect(gitlab_shell.remove_repository('current/storage', 'project/path')).to be false end @@ -304,7 +333,7 @@ describe Gitlab::Shell do end end - describe '#fetch_remote local', skip_gitaly_mock: true do + describe '#fetch_remote local', :skip_gitaly_mock do it_should_behave_like 'fetch_remote', false end @@ -330,4 +359,52 @@ describe Gitlab::Shell do end end end + + describe 'namespace actions' do + subject { described_class.new } + let(:storage_path) { Gitlab.config.repositories.storages.default.path } + + describe '#add_namespace' do + it 'creates a namespace' do + subject.add_namespace(storage_path, "mepmep") + + expect(subject.exists?(storage_path, "mepmep")).to be(true) + end + end + + describe '#exists?' do + context 'when the namespace does not exist' do + it 'returns false' do + expect(subject.exists?(storage_path, "non-existing")).to be(false) + end + end + + context 'when the namespace exists' do + it 'returns true' do + subject.add_namespace(storage_path, "mepmep") + + expect(subject.exists?(storage_path, "mepmep")).to be(true) + end + end + end + + describe '#remove' do + it 'removes the namespace' do + subject.add_namespace(storage_path, "mepmep") + subject.rm_namespace(storage_path, "mepmep") + + expect(subject.exists?(storage_path, "mepmep")).to be(false) + end + end + + describe '#mv_namespace' do + it 'renames the namespace' do + subject.add_namespace(storage_path, "mepmep") + subject.mv_namespace(storage_path, "mepmep", "2mep") + + expect(subject.exists?(storage_path, "mepmep")).to be(false) + expect(subject.exists?(storage_path, "2mep")).to be(true) + end + end + end end diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb new file mode 100644 index 00000000000..8fdbbacd04d --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::MemoryKiller do + subject { described_class.new } + let(:pid) { 999 } + + let(:worker) { double(:worker, class: 'TestWorker') } + let(:job) { { 'jid' => 123 } } + let(:queue) { 'test_queue' } + + def run + thread = subject.call(worker, job, queue) { nil } + thread&.join + end + + before do + allow(subject).to receive(:get_rss).and_return(10.kilobytes) + allow(subject).to receive(:pid).and_return(pid) + end + + context 'when MAX_RSS is set to 0' do + before do + stub_const("#{described_class}::MAX_RSS", 0) + end + + it 'does nothing' do + expect(subject).not_to receive(:sleep) + + run + end + end + + context 'when MAX_RSS is exceeded' do + before do + stub_const("#{described_class}::MAX_RSS", 5.kilobytes) + end + + it 'sends the STP, TERM and KILL signals at expected times' do + expect(subject).to receive(:sleep).with(15 * 60).ordered + expect(Process).to receive(:kill).with('SIGSTP', pid).ordered + + expect(subject).to receive(:sleep).with(30).ordered + expect(Process).to receive(:kill).with('SIGTERM', pid).ordered + + expect(subject).to receive(:sleep).with(10).ordered + expect(Process).to receive(:kill).with('SIGKILL', pid).ordered + + run + end + end + + context 'when MAX_RSS is not exceeded' do + before do + stub_const("#{described_class}::MAX_RSS", 15.kilobytes) + end + + it 'does nothing' do + expect(subject).not_to receive(:sleep) + + run + end + end +end diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index c2e77ef6b6c..884f27b212c 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -39,6 +39,18 @@ describe Gitlab::SidekiqStatus do end end + describe '.running?', :clean_gitlab_redis_shared_state do + it 'returns true if job is running' do + described_class.set('123') + + expect(described_class.running?('123')).to be(true) + end + + it 'returns false if job is not found' do + expect(described_class.running?('123')).to be(false) + end + end + describe '.num_running', :clean_gitlab_redis_shared_state do it 'returns 0 if all jobs have been completed' do expect(described_class.num_running(%w(123))).to eq(0) diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb index baf8f6644bf..fe6422c32b6 100644 --- a/spec/lib/gitlab/sql/union_spec.rb +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -22,5 +22,19 @@ describe Gitlab::SQL::Union do expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}") end + + it 'uses UNION ALL when removing duplicates is disabled' do + union = described_class + .new([relation_1, relation_2], remove_duplicates: false) + + expect(union.to_sql).to include('UNION ALL') + end + + it 'returns `NULL` if all relations are empty' do + empty_relation = User.none + union = described_class.new([empty_relation, empty_relation]) + + expect(union.to_sql).to eq('NULL') + end end end diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 59c28431e1e..fc8991fd31f 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -39,7 +39,8 @@ describe Gitlab::UrlSanitizer do false | nil false | '' false | '123://invalid:url' - true | 'valid@project:url.git' + false | 'valid@project:url.git' + false | 'valid:pass@project:url.git' true | 'ssh://example.com' true | 'ssh://:@example.com' true | 'ssh://foo@example.com' @@ -81,24 +82,6 @@ describe Gitlab::UrlSanitizer do describe '#credentials' do context 'credentials in hash' do - where(:input, :output) do - { user: 'foo', password: 'bar' } | { user: 'foo', password: 'bar' } - { user: 'foo', password: '' } | { user: 'foo', password: nil } - { user: 'foo', password: nil } | { user: 'foo', password: nil } - { user: '', password: 'bar' } | { user: nil, password: 'bar' } - { user: '', password: '' } | { user: nil, password: nil } - { user: '', password: nil } | { user: nil, password: nil } - { user: nil, password: 'bar' } | { user: nil, password: 'bar' } - { user: nil, password: '' } | { user: nil, password: nil } - { user: nil, password: nil } | { user: nil, password: nil } - end - - with_them do - subject { described_class.new('user@example.com:path.git', credentials: input).credentials } - - it { is_expected.to eq(output) } - end - it 'overrides URL-provided credentials' do sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' }) @@ -116,10 +99,6 @@ describe Gitlab::UrlSanitizer do 'http://@example.com' | { user: nil, password: nil } 'http://example.com' | { user: nil, password: nil } - # Credentials from SCP-style URLs are not supported at present - 'foo@example.com:path' | { user: nil, password: nil } - 'foo:bar@example.com:path' | { user: nil, password: nil } - # Other invalid URLs nil | { user: nil, password: nil } '' | { user: nil, password: nil } diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index ee152872acc..a7b65e94706 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -60,6 +60,9 @@ describe Gitlab::UsageData do deploy_keys deployments environments + gcp_clusters + gcp_clusters_enabled + gcp_clusters_disabled in_review_folder groups issues diff --git a/spec/lib/gitlab/utils/merge_hash_spec.rb b/spec/lib/gitlab/utils/merge_hash_spec.rb new file mode 100644 index 00000000000..4fa7bb31301 --- /dev/null +++ b/spec/lib/gitlab/utils/merge_hash_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +describe Gitlab::Utils::MergeHash do + describe '.crush' do + it 'can flatten a hash to each element' do + input = { hello: "world", this: { crushes: ["an entire", "hash"] } } + expected_result = [:hello, "world", :this, :crushes, "an entire", "hash"] + + expect(described_class.crush(input)).to eq(expected_result) + end + end + + describe '.elements' do + it 'deep merges an array of elements' do + input = [{ hello: ["world"] }, + { hello: "Everyone" }, + { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'DzieÅ„ dobry'] } }, + "Goodbye", "Hallo"] + expected_output = [ + { + hello: + [ + "world", + "Everyone", + { greetings: ['Bonjour', 'Hello', 'Hallo', 'DzieÅ„ dobry'] } + ] + }, + "Goodbye" + ] + + expect(described_class.merge(input)).to eq(expected_output) + end + end +end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 699184ad9fe..249c77dc636 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -13,13 +13,51 @@ describe Gitlab::Workhorse do end describe ".send_git_archive" do + let(:ref) { 'master' } + let(:format) { 'zip' } + let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path } + let(:base_params) { repository.archive_metadata(ref, storage_path, format) } + let(:gitaly_params) do + base_params.merge( + 'GitalyServer' => { + 'address' => Gitlab::GitalyClient.address(project.repository_storage), + 'token' => Gitlab::GitalyClient.token(project.repository_storage) + }, + 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys + ) + end + + subject do + described_class.send_git_archive(repository, ref: ref, format: format) + end + + context 'when Gitaly workhorse_archive feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq('Gitlab-Workhorse-Send-Data') + expect(command).to eq('git-archive') + expect(params).to include(gitaly_params) + end + end + + context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq('Gitlab-Workhorse-Send-Data') + expect(command).to eq('git-archive') + expect(params).to eq(base_params) + end + end + context "when the repository doesn't have an archive file path" do before do allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) end it "raises an error" do - expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError) + expect { subject }.to raise_error(RuntimeError) end end end @@ -28,12 +66,34 @@ describe Gitlab::Workhorse do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } subject { described_class.send_git_patch(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_patch feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawPatchRequest' => Gitaly::RawPatchRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_patch feature is disabled', :skip_gitaly_mock do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end @@ -77,14 +137,36 @@ describe Gitlab::Workhorse do describe '.send_git_diff' do let(:diff_refs) { double(base_sha: "base", head_sha: "head") } - subject { described_class.send_git_patch(repository, diff_refs) } + subject { described_class.send_git_diff(repository, diff_refs) } - it 'sets the header correctly' do - key, command, params = decode_workhorse_header(subject) + context 'when Gitaly workhorse_send_git_diff feature is enabled' do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) - expect(key).to eq("Gitlab-Workhorse-Send-Data") - expect(command).to eq("git-format-patch") - expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq({ + 'GitalyServer' => { + address: Gitlab::GitalyClient.address(project.repository_storage), + token: Gitlab::GitalyClient.token(project.repository_storage) + }, + 'RawDiffRequest' => Gitaly::RawDiffRequest.new( + repository: repository.gitaly_repository, + left_commit_id: 'base', + right_commit_id: 'head' + ).to_json + }.deep_stringify_keys) + end + end + + context 'when Gitaly workhorse_send_git_diff feature is disabled', :skip_gitaly_mock do + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-diff") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end end end @@ -182,7 +264,13 @@ describe Gitlab::Workhorse do let(:repo_path) { repository.path_to_repo } let(:action) { 'info_refs' } let(:params) do - { GL_ID: "user-#{user.id}", GL_REPOSITORY: "project-#{project.id}", RepoPath: repo_path } + { + GL_ID: "user-#{user.id}", + GL_USERNAME: user.username, + GL_REPOSITORY: "project-#{project.id}", + RepoPath: repo_path, + ShowAllRefs: false + } end subject { described_class.git_http_ok(repository, false, user, action) } @@ -191,7 +279,13 @@ describe Gitlab::Workhorse do context 'when is_wiki' do let(:params) do - { GL_ID: "user-#{user.id}", GL_REPOSITORY: "wiki-#{project.id}", RepoPath: repo_path } + { + GL_ID: "user-#{user.id}", + GL_USERNAME: user.username, + GL_REPOSITORY: "wiki-#{project.id}", + RepoPath: repo_path, + ShowAllRefs: false + } end subject { described_class.git_http_ok(repository, true, user, action) } @@ -214,14 +308,13 @@ describe Gitlab::Workhorse do end it 'includes a Repository param' do - repo_param = { Repository: { + repo_param = { storage_name: 'default', relative_path: project.full_path + '.git', - git_object_directory: '', - git_alternate_object_directories: [] - } } + gl_repository: "project-#{project.id}" + } - expect(subject).to include(repo_param) + expect(subject[:Repository]).to include(repo_param) end context "when git_upload_pack action is passed" do @@ -233,6 +326,12 @@ describe Gitlab::Workhorse do expect(subject).to include(gitaly_params) end + + context 'show_all_refs enabled' do + subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + + it { is_expected.to include(ShowAllRefs: true) } + end end context "when git_receive_pack action is passed" do @@ -245,6 +344,12 @@ describe Gitlab::Workhorse do let(:action) { 'info_refs' } it { expect(subject).to include(gitaly_params) } + + context 'show_all_refs enabled' do + subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + + it { is_expected.to include(ShowAllRefs: true) } + end end context 'when action passed is not supported by Gitaly' do @@ -336,7 +441,7 @@ describe Gitlab::Workhorse do end end - context 'when Gitaly workhorse_raw_show feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly workhorse_raw_show feature is disabled', :skip_gitaly_mock do it 'sets the header correctly' do key, command, params = decode_workhorse_header(subject) diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb new file mode 100644 index 00000000000..87a3f43274f --- /dev/null +++ b/spec/lib/google_api/auth_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe GoogleApi::Auth do + let(:redirect_uri) { 'http://localhost:3000/google_api/authorizations/callback' } + let(:redirect_to) { 'http://localhost:3000/namaspace/project/clusters' } + + let(:client) do + GoogleApi::CloudPlatform::Client + .new(nil, redirect_uri, state: redirect_to) + end + + describe '#authorize_url' do + subject { client.authorize_url } + + it 'returns authorize_url' do + is_expected.to start_with('https://accounts.google.com/o/oauth2') + is_expected.to include(URI.encode(redirect_uri, URI::PATTERN::RESERVED)) + is_expected.to include(URI.encode(redirect_to, URI::PATTERN::RESERVED)) + end + end + + describe '#get_token' do + let(:token) do + double.tap do |dbl| + allow(dbl).to receive(:token).and_return('token') + allow(dbl).to receive(:expires_at).and_return('expires_at') + end + end + + before do + allow_any_instance_of(OAuth2::Strategy::AuthCode) + .to receive(:get_token).and_return(token) + end + + it 'returns token and expires_at' do + token, expires_at = client.get_token('xxx') + expect(token).to eq('token') + expect(expires_at).to eq('expires_at') + end + end +end diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb new file mode 100644 index 00000000000..acc5bd1da35 --- /dev/null +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +describe GoogleApi::CloudPlatform::Client do + let(:token) { 'token' } + let(:client) { described_class.new(token, nil) } + + describe '.session_key_for_redirect_uri' do + let(:state) { 'random_string' } + + subject { described_class.session_key_for_redirect_uri(state) } + + it 'creates a new session key' do + is_expected.to eq('cloud_platform_second_redirect_uri_random_string') + end + end + + describe '.new_session_key_for_redirect_uri' do + it 'generates a new session key' do + expect { |b| described_class.new_session_key_for_redirect_uri(&b) } + .to yield_with_args(String) + end + end + + describe '#validate_token' do + subject { client.validate_token(expires_at) } + + let(:expires_at) { 1.hour.since.utc.strftime('%s') } + + context 'when token is nil' do + let(:token) { nil } + + it { is_expected.to be_falsy } + end + + context 'when expires_at is nil' do + let(:expires_at) { nil } + + it { is_expected.to be_falsy } + end + + context 'when expires in 1 hour' do + it { is_expected.to be_truthy } + end + + context 'when expires in 10 minutes' do + let(:expires_at) { 5.minutes.since.utc.strftime('%s') } + + it { is_expected.to be_falsy } + end + end + + describe '#projects_zones_clusters_get' do + subject { client.projects_zones_clusters_get(spy, spy, spy) } + let(:gke_cluster) { double } + + before do + allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) + .to receive(:get_zone_cluster).and_return(gke_cluster) + end + + it { is_expected.to eq(gke_cluster) } + end + + describe '#projects_zones_clusters_create' do + subject do + client.projects_zones_clusters_create( + spy, spy, cluster_name, cluster_size, machine_type: machine_type) + end + + let(:cluster_name) { 'test-cluster' } + let(:cluster_size) { 1 } + let(:machine_type) { 'n1-standard-4' } + let(:operation) { double } + + before do + allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) + .to receive(:create_cluster).and_return(operation) + end + + it { is_expected.to eq(operation) } + + it 'sets corresponded parameters' do + expect_any_instance_of(Google::Apis::ContainerV1::CreateClusterRequest) + .to receive(:initialize).with( + { + "cluster": { + "name": cluster_name, + "initial_node_count": cluster_size, + "node_config": { + "machine_type": machine_type + } + } + } ) + + subject + end + end + + describe '#projects_zones_operations' do + subject { client.projects_zones_operations(spy, spy, spy) } + let(:operation) { double } + + before do + allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) + .to receive(:get_zone_operation).and_return(operation) + end + + it { is_expected.to eq(operation) } + end + + describe '#parse_operation_id' do + subject { client.parse_operation_id(self_link) } + + context 'when expected url' do + let(:self_link) do + 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123' + end + + it { is_expected.to eq('ope-123') } + end + + context 'when unexpected url' do + let(:self_link) { '???' } + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb new file mode 100644 index 00000000000..83556787e85 --- /dev/null +++ b/spec/lib/rspec_flaky/config_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +describe RspecFlaky::Config, :aggregate_failures do + before do + # Stub these env variables otherwise specs don't behave the same on the CI + stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil) + stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil) + stub_env('FLAKY_RSPEC_REPORT_PATH', nil) + stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', nil) + end + + describe '.generate_report?' do + context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is not set" do + it 'returns false' do + expect(described_class).not_to be_generate_report + end + end + + context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'false'" do + before do + stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false') + end + + it 'returns false' do + expect(described_class).not_to be_generate_report + end + end + + context "when ENV['FLAKY_RSPEC_GENERATE_REPORT'] is set to 'true'" do + before do + stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true') + end + + it 'returns true' do + expect(described_class).to be_generate_report + end + end + end + + describe '.suite_flaky_examples_report_path' do + context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is not set" do + it 'returns the default path' do + expect(Rails.root).to receive(:join).with('rspec_flaky/suite-report.json') + .and_return('root/rspec_flaky/suite-report.json') + + expect(described_class.suite_flaky_examples_report_path).to eq('root/rspec_flaky/suite-report.json') + end + end + + context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is set" do + before do + stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', 'foo/suite-report.json') + end + + it 'returns the value of the env variable' do + expect(described_class.suite_flaky_examples_report_path).to eq('foo/suite-report.json') + end + end + end + + describe '.flaky_examples_report_path' do + context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is not set" do + it 'returns the default path' do + expect(Rails.root).to receive(:join).with('rspec_flaky/report.json') + .and_return('root/rspec_flaky/report.json') + + expect(described_class.flaky_examples_report_path).to eq('root/rspec_flaky/report.json') + end + end + + context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is set" do + before do + stub_env('FLAKY_RSPEC_REPORT_PATH', 'foo/report.json') + end + + it 'returns the value of the env variable' do + expect(described_class.flaky_examples_report_path).to eq('foo/report.json') + end + end + end + + describe '.new_flaky_examples_report_path' do + context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is not set" do + it 'returns the default path' do + expect(Rails.root).to receive(:join).with('rspec_flaky/new-report.json') + .and_return('root/rspec_flaky/new-report.json') + + expect(described_class.new_flaky_examples_report_path).to eq('root/rspec_flaky/new-report.json') + end + end + + context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is set" do + before do + stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', 'foo/new-report.json') + end + + it 'returns the value of the env variable' do + expect(described_class.new_flaky_examples_report_path).to eq('foo/new-report.json') + end + end + end +end diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb index cbfc1e538ab..d19c34bebb3 100644 --- a/spec/lib/rspec_flaky/flaky_example_spec.rb +++ b/spec/lib/rspec_flaky/flaky_example_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe RspecFlaky::FlakyExample do +describe RspecFlaky::FlakyExample, :aggregate_failures do let(:flaky_example_attrs) do { example_id: 'spec/foo/bar_spec.rb:2', @@ -9,6 +9,7 @@ describe RspecFlaky::FlakyExample do description: 'hello world', first_flaky_at: 1234, last_flaky_at: 2345, + last_flaky_job: 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/12', last_attempts_count: 2, flaky_reports: 1 } @@ -27,57 +28,78 @@ describe RspecFlaky::FlakyExample do end let(:example) { double(example_attrs) } + before do + # Stub these env variables otherwise specs don't behave the same on the CI + stub_env('CI_PROJECT_URL', nil) + stub_env('CI_JOB_ID', nil) + end + describe '#initialize' do shared_examples 'a valid FlakyExample instance' do - it 'returns valid attributes' do - flaky_example = described_class.new(args) + let(:flaky_example) { described_class.new(args) } + it 'returns valid attributes' do expect(flaky_example.uid).to eq(flaky_example_attrs[:uid]) - expect(flaky_example.example_id).to eq(flaky_example_attrs[:example_id]) + expect(flaky_example.file).to eq(flaky_example_attrs[:file]) + expect(flaky_example.line).to eq(flaky_example_attrs[:line]) + expect(flaky_example.description).to eq(flaky_example_attrs[:description]) + expect(flaky_example.first_flaky_at).to eq(expected_first_flaky_at) + expect(flaky_example.last_flaky_at).to eq(expected_last_flaky_at) + expect(flaky_example.last_attempts_count).to eq(flaky_example_attrs[:last_attempts_count]) + expect(flaky_example.flaky_reports).to eq(expected_flaky_reports) end end context 'when given an Rspec::Example' do - let(:args) { example } - - it_behaves_like 'a valid FlakyExample instance' + it_behaves_like 'a valid FlakyExample instance' do + let(:args) { example } + let(:expected_first_flaky_at) { nil } + let(:expected_last_flaky_at) { nil } + let(:expected_flaky_reports) { 0 } + end end context 'when given a hash' do - let(:args) { flaky_example_attrs } - - it_behaves_like 'a valid FlakyExample instance' + it_behaves_like 'a valid FlakyExample instance' do + let(:args) { flaky_example_attrs } + let(:expected_flaky_reports) { flaky_example_attrs[:flaky_reports] } + let(:expected_first_flaky_at) { flaky_example_attrs[:first_flaky_at] } + let(:expected_last_flaky_at) { flaky_example_attrs[:last_flaky_at] } + end end end - describe '#to_h' do - before do - # Stub these env variables otherwise specs don't behave the same on the CI - stub_env('CI_PROJECT_URL', nil) - stub_env('CI_JOB_ID', nil) - end + describe '#update_flakiness!' do + shared_examples 'an up-to-date FlakyExample instance' do + let(:flaky_example) { described_class.new(args) } - shared_examples 'a valid FlakyExample hash' do - let(:additional_attrs) { {} } + it 'updates the first_flaky_at' do + now = Time.now + expected_first_flaky_at = flaky_example.first_flaky_at ? flaky_example.first_flaky_at : now + Timecop.freeze(now) { flaky_example.update_flakiness! } - it 'returns a valid hash' do - flaky_example = described_class.new(args) - final_hash = flaky_example_attrs - .merge(last_flaky_at: instance_of(Time), last_flaky_job: nil) - .merge(additional_attrs) + expect(flaky_example.first_flaky_at).to eq(expected_first_flaky_at) + end + + it 'updates the last_flaky_at' do + now = Time.now + Timecop.freeze(now) { flaky_example.update_flakiness! } - expect(flaky_example.to_h).to match(hash_including(final_hash)) + expect(flaky_example.last_flaky_at).to eq(now) end - end - context 'when given an Rspec::Example' do - let(:args) { example } + it 'updates the flaky_reports' do + expected_flaky_reports = flaky_example.first_flaky_at ? flaky_example.flaky_reports + 1 : 1 + + expect { flaky_example.update_flakiness! }.to change { flaky_example.flaky_reports }.by(1) + expect(flaky_example.flaky_reports).to eq(expected_flaky_reports) + end + + context 'when passed a :last_attempts_count' do + it 'updates the last_attempts_count' do + flaky_example.update_flakiness!(last_attempts_count: 42) - context 'when run locally' do - it_behaves_like 'a valid FlakyExample hash' do - let(:additional_attrs) do - { first_flaky_at: instance_of(Time) } - end + expect(flaky_example.last_attempts_count).to eq(42) end end @@ -87,10 +109,45 @@ describe RspecFlaky::FlakyExample do stub_env('CI_JOB_ID', 42) end - it_behaves_like 'a valid FlakyExample hash' do - let(:additional_attrs) do - { first_flaky_at: instance_of(Time), last_flaky_job: "https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/42" } - end + it 'updates the last_flaky_job' do + flaky_example.update_flakiness! + + expect(flaky_example.last_flaky_job).to eq('https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/42') + end + end + end + + context 'when given an Rspec::Example' do + it_behaves_like 'an up-to-date FlakyExample instance' do + let(:args) { example } + end + end + + context 'when given a hash' do + it_behaves_like 'an up-to-date FlakyExample instance' do + let(:args) { flaky_example_attrs } + end + end + end + + describe '#to_h' do + shared_examples 'a valid FlakyExample hash' do + let(:additional_attrs) { {} } + + it 'returns a valid hash' do + flaky_example = described_class.new(args) + final_hash = flaky_example_attrs.merge(additional_attrs) + + expect(flaky_example.to_h).to eq(final_hash) + end + end + + context 'when given an Rspec::Example' do + let(:args) { example } + + it_behaves_like 'a valid FlakyExample hash' do + let(:additional_attrs) do + { first_flaky_at: nil, last_flaky_at: nil, last_flaky_job: nil, flaky_reports: 0 } end end end diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb new file mode 100644 index 00000000000..06a8ba0d02e --- /dev/null +++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do + let(:collection_hash) do + { + a: { example_id: 'spec/foo/bar_spec.rb:2' }, + b: { example_id: 'spec/foo/baz_spec.rb:3' } + } + end + let(:collection_report) do + { + a: { + example_id: 'spec/foo/bar_spec.rb:2', + first_flaky_at: nil, + last_flaky_at: nil, + last_flaky_job: nil + }, + b: { + example_id: 'spec/foo/baz_spec.rb:3', + first_flaky_at: nil, + last_flaky_at: nil, + last_flaky_job: nil + } + } + end + + describe '.from_json' do + it 'accepts a JSON' do + collection = described_class.from_json(JSON.pretty_generate(collection_hash)) + + expect(collection.to_report).to eq(described_class.new(collection_hash).to_report) + end + end + + describe '#initialize' do + it 'accepts no argument' do + expect { described_class.new }.not_to raise_error + end + + it 'accepts a hash' do + expect { described_class.new(collection_hash) }.not_to raise_error + end + + it 'does not accept anything else' do + expect { described_class.new([1, 2, 3]) }.to raise_error(ArgumentError, "`collection` must be a Hash, Array given!") + end + end + + describe '#to_report' do + it 'calls #to_h on the values' do + collection = described_class.new(collection_hash) + + expect(collection.to_report).to eq(collection_report) + end + end + + describe '#-' do + it 'returns only examples that are not present in the given collection' do + collection1 = described_class.new(collection_hash) + collection2 = described_class.new( + a: { example_id: 'spec/foo/bar_spec.rb:2' }, + c: { example_id: 'spec/bar/baz_spec.rb:4' }) + + expect((collection2 - collection1).to_report).to eq( + c: { + example_id: 'spec/bar/baz_spec.rb:4', + first_flaky_at: nil, + last_flaky_at: nil, + last_flaky_job: nil + }) + end + + it 'fails if the given collection does not respond to `#key?`' do + collection = described_class.new(collection_hash) + + expect { collection - [1, 2, 3] }.to raise_error(ArgumentError, "`other` must respond to `#key?`, Array does not!") + end + end +end diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb index 0e193bf408b..bfb7648b486 100644 --- a/spec/lib/rspec_flaky/listener_spec.rb +++ b/spec/lib/rspec_flaky/listener_spec.rb @@ -1,22 +1,35 @@ require 'spec_helper' -describe RspecFlaky::Listener do - let(:flaky_example_report) do +describe RspecFlaky::Listener, :aggregate_failures do + let(:already_flaky_example_uid) { '6e869794f4cfd2badd93eb68719371d1' } + let(:suite_flaky_example_report) do { - 'abc123' => { + already_flaky_example_uid => { example_id: 'spec/foo/bar_spec.rb:2', file: 'spec/foo/bar_spec.rb', line: 2, description: 'hello world', first_flaky_at: 1234, - last_flaky_at: instance_of(Time), - last_attempts_count: 2, + last_flaky_at: 4321, + last_attempts_count: 3, flaky_reports: 1, last_flaky_job: nil } } end - let(:example_attrs) do + let(:already_flaky_example_attrs) do + { + id: 'spec/foo/bar_spec.rb:2', + metadata: { + file_path: 'spec/foo/bar_spec.rb', + line_number: 2, + full_description: 'hello world' + }, + execution_result: double(status: 'passed', exception: nil) + } + end + let(:already_flaky_example) { RspecFlaky::FlakyExample.new(suite_flaky_example_report[already_flaky_example_uid]) } + let(:new_example_attrs) do { id: 'spec/foo/baz_spec.rb:3', metadata: { @@ -32,18 +45,19 @@ describe RspecFlaky::Listener do # Stub these env variables otherwise specs don't behave the same on the CI stub_env('CI_PROJECT_URL', nil) stub_env('CI_JOB_ID', nil) + stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil) end describe '#initialize' do shared_examples 'a valid Listener instance' do - let(:expected_all_flaky_examples) { {} } + let(:expected_suite_flaky_examples) { {} } it 'returns a valid Listener instance' do listener = described_class.new - expect(listener.to_report(listener.all_flaky_examples)) - .to match(hash_including(expected_all_flaky_examples)) - expect(listener.new_flaky_examples).to eq({}) + expect(listener.to_report(listener.suite_flaky_examples)) + .to eq(expected_suite_flaky_examples) + expect(listener.flaky_examples).to eq({}) end end @@ -51,16 +65,16 @@ describe RspecFlaky::Listener do it_behaves_like 'a valid Listener instance' end - context 'when a report file exists and set by ALL_FLAKY_RSPEC_REPORT_PATH' do + context 'when a report file exists and set by SUITE_FLAKY_RSPEC_REPORT_PATH' do let(:report_file) do Tempfile.new(%w[rspec_flaky_report .json]).tap do |f| - f.write(JSON.pretty_generate(flaky_example_report)) + f.write(JSON.pretty_generate(suite_flaky_example_report)) f.rewind end end before do - stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file.path) + stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file.path) end after do @@ -69,74 +83,122 @@ describe RspecFlaky::Listener do end it_behaves_like 'a valid Listener instance' do - let(:expected_all_flaky_examples) { flaky_example_report } + let(:expected_suite_flaky_examples) { suite_flaky_example_report } end end end describe '#example_passed' do - let(:rspec_example) { double(example_attrs) } + let(:rspec_example) { double(new_example_attrs) } let(:notification) { double(example: rspec_example) } + let(:listener) { described_class.new(suite_flaky_example_report.to_json) } shared_examples 'a non-flaky example' do it 'does not change the flaky examples hash' do - expect { subject.example_passed(notification) } - .not_to change { subject.all_flaky_examples } + expect { listener.example_passed(notification) } + .not_to change { listener.flaky_examples } end end - describe 'when the RSpec example does not respond to attempts' do - it_behaves_like 'a non-flaky example' - end + shared_examples 'an existing flaky example' do + let(:expected_flaky_example) do + { + example_id: 'spec/foo/bar_spec.rb:2', + file: 'spec/foo/bar_spec.rb', + line: 2, + description: 'hello world', + first_flaky_at: 1234, + last_attempts_count: 2, + flaky_reports: 2, + last_flaky_job: nil + } + end - describe 'when the RSpec example has 1 attempt' do - let(:rspec_example) { double(example_attrs.merge(attempts: 1)) } + it 'changes the flaky examples hash' do + new_example = RspecFlaky::Example.new(rspec_example) - it_behaves_like 'a non-flaky example' + now = Time.now + Timecop.freeze(now) do + expect { listener.example_passed(notification) } + .to change { listener.flaky_examples[new_example.uid].to_h } + end + + expect(listener.flaky_examples[new_example.uid].to_h) + .to eq(expected_flaky_example.merge(last_flaky_at: now)) + end end - describe 'when the RSpec example has 2 attempts' do - let(:rspec_example) { double(example_attrs.merge(attempts: 2)) } - let(:expected_new_flaky_example) do + shared_examples 'a new flaky example' do + let(:expected_flaky_example) do { example_id: 'spec/foo/baz_spec.rb:3', file: 'spec/foo/baz_spec.rb', line: 3, description: 'hello GitLab', - first_flaky_at: instance_of(Time), - last_flaky_at: instance_of(Time), last_attempts_count: 2, flaky_reports: 1, last_flaky_job: nil } end - it 'does not change the flaky examples hash' do - expect { subject.example_passed(notification) } - .to change { subject.all_flaky_examples } - + it 'changes the all flaky examples hash' do new_example = RspecFlaky::Example.new(rspec_example) - expect(subject.all_flaky_examples[new_example.uid].to_h) - .to match(hash_including(expected_new_flaky_example)) + now = Time.now + Timecop.freeze(now) do + expect { listener.example_passed(notification) } + .to change { listener.flaky_examples[new_example.uid].to_h } + end + + expect(listener.flaky_examples[new_example.uid].to_h) + .to eq(expected_flaky_example.merge(first_flaky_at: now, last_flaky_at: now)) + end + end + + describe 'when the RSpec example does not respond to attempts' do + it_behaves_like 'a non-flaky example' + end + + describe 'when the RSpec example has 1 attempt' do + let(:rspec_example) { double(new_example_attrs.merge(attempts: 1)) } + + it_behaves_like 'a non-flaky example' + end + + describe 'when the RSpec example has 2 attempts' do + let(:rspec_example) { double(new_example_attrs.merge(attempts: 2)) } + + it_behaves_like 'a new flaky example' + + context 'with an existing flaky example' do + let(:rspec_example) { double(already_flaky_example_attrs.merge(attempts: 2)) } + + it_behaves_like 'an existing flaky example' end end end describe '#dump_summary' do - let(:rspec_example) { double(example_attrs) } - let(:notification) { double(example: rspec_example) } + let(:listener) { described_class.new(suite_flaky_example_report.to_json) } + let(:new_flaky_rspec_example) { double(new_example_attrs.merge(attempts: 2)) } + let(:already_flaky_rspec_example) { double(already_flaky_example_attrs.merge(attempts: 2)) } + let(:notification_new_flaky_rspec_example) { double(example: new_flaky_rspec_example) } + let(:notification_already_flaky_rspec_example) { double(example: already_flaky_rspec_example) } - context 'when a report file path is set by ALL_FLAKY_RSPEC_REPORT_PATH' do + context 'when a report file path is set by FLAKY_RSPEC_REPORT_PATH' do let(:report_file_path) { Rails.root.join('tmp', 'rspec_flaky_report.json') } + let(:new_report_file_path) { Rails.root.join('tmp', 'rspec_flaky_new_report.json') } before do - stub_env('ALL_FLAKY_RSPEC_REPORT_PATH', report_file_path) + stub_env('FLAKY_RSPEC_REPORT_PATH', report_file_path) + stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', new_report_file_path) FileUtils.rm(report_file_path) if File.exist?(report_file_path) + FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path) end after do FileUtils.rm(report_file_path) if File.exist?(report_file_path) + FileUtils.rm(new_report_file_path) if File.exist?(new_report_file_path) end context 'when FLAKY_RSPEC_GENERATE_REPORT == "false"' do @@ -144,12 +206,13 @@ describe RspecFlaky::Listener do stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'false') end - it 'does not write the report file' do - subject.example_passed(notification) + it 'does not write any report file' do + listener.example_passed(notification_new_flaky_rspec_example) - subject.dump_summary(nil) + listener.dump_summary(nil) expect(File.exist?(report_file_path)).to be(false) + expect(File.exist?(new_report_file_path)).to be(false) end end @@ -158,21 +221,39 @@ describe RspecFlaky::Listener do stub_env('FLAKY_RSPEC_GENERATE_REPORT', 'true') end - it 'writes the report file' do - subject.example_passed(notification) + around do |example| + Timecop.freeze { example.run } + end + + it 'writes the report files' do + listener.example_passed(notification_new_flaky_rspec_example) + listener.example_passed(notification_already_flaky_rspec_example) - subject.dump_summary(nil) + listener.dump_summary(nil) expect(File.exist?(report_file_path)).to be(true) + expect(File.exist?(new_report_file_path)).to be(true) + + expect(File.read(report_file_path)) + .to eq(JSON.pretty_generate(listener.to_report(listener.flaky_examples))) + + new_example = RspecFlaky::Example.new(notification_new_flaky_rspec_example) + new_flaky_example = RspecFlaky::FlakyExample.new(new_example) + new_flaky_example.update_flakiness! + + expect(File.read(new_report_file_path)) + .to eq(JSON.pretty_generate(listener.to_report(new_example.uid => new_flaky_example))) end end end end describe '#to_report' do + let(:listener) { described_class.new(suite_flaky_example_report.to_json) } + it 'transforms the internal hash to a JSON-ready hash' do - expect(subject.to_report('abc123' => RspecFlaky::FlakyExample.new(flaky_example_report['abc123']))) - .to match(hash_including(flaky_example_report)) + expect(listener.to_report(already_flaky_example_uid => already_flaky_example)) + .to match(hash_including(suite_flaky_example_report)) end end end diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb index 7125bfcab59..a0fb86345f3 100644 --- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb +++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb @@ -16,7 +16,12 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do end it 'only whitelists safe files' do - expect(described_class::WHITELIST).to contain_exactly('authorized_keys', 'authorized_keys2', 'known_hosts') + expect(described_class::WHITELIST).to contain_exactly( + 'authorized_keys', + 'authorized_keys2', + 'authorized_keys.lock', + 'known_hosts' + ) end describe '#skip?' do diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 09e5094cf84..1f7be415e35 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -120,29 +120,4 @@ describe Emails::Profile do it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error } end end - - describe 'user added email' do - let(:email) { create(:email) } - - subject { Notify.new_email_email(email.id) } - - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like 'a user cannot unsubscribe through footer link' - - it 'is sent to the new user' do - is_expected.to deliver_to email.user.email - end - - it 'has the correct subject' do - is_expected.to have_subject /^Email was added to your account$/i - end - - it 'contains the new email address' do - is_expected.to have_body_text /#{email.email}/ - end - - it 'includes a link to emails page' do - is_expected.to have_body_text /#{profile_emails_path}/ - end - end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 932e2fd8c95..c832cee965b 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -28,319 +28,334 @@ describe Notify do end def have_referable_subject(referable, reply: false) - prefix = referable.project.name if referable.project - prefix = "Re: #{prefix}" if reply + prefix = referable.project ? "#{referable.project.name} | " : '' + prefix.prepend('Re: ') if reply suffix = "#{referable.title} (#{referable.to_reference})" - have_subject [prefix, suffix].compact.join(' | ') + have_subject [prefix, suffix].compact.join end context 'for a project' do - describe 'items that are assignable, the email' do - let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + shared_examples 'an assignee email' do + it 'is sent to the assignee as the author' do + sender = subject.header[:from].addrs.first + + aggregate_failures do + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + expect(subject).to deliver_to(assignee.email) + end + end + end - shared_examples 'an assignee email' do - it 'is sent to the assignee as the author' do - sender = subject.header[:from].addrs.first + context 'for issues' do + describe 'that are new' do + subject { described_class.new_issue_email(issue.assignees.first.id, issue.id) } + it_behaves_like 'an assignee email' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'has the correct subject and body' do aggregate_failures do - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - expect(subject).to deliver_to(assignee.email) + is_expected.to have_referable_subject(issue) + is_expected.to have_body_text(project_issue_path(project, issue)) end end - end - context 'for issues' do - describe 'that are new' do - subject { described_class.new_issue_email(issue.assignees.first.id, issue.id) } + it 'contains the description' do + is_expected.to have_html_escaped_body_text issue.description + end - it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' - - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue) - is_expected.to have_body_text(project_issue_path(project, issue)) - end + context 'when enabled email_author_in_body' do + before do + stub_application_setting(email_author_in_body: true) end - it 'contains the description' do - is_expected.to have_html_escaped_body_text issue.description + it 'contains a link to note author' do + is_expected.to have_html_escaped_body_text(issue.author_name) + is_expected.to have_body_text 'created an issue:' end + end + end - context 'when enabled email_author_in_body' do - before do - stub_application_setting(email_author_in_body: true) - end + describe 'that are reassigned' do + let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id) } - it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text(issue.author_name) - is_expected.to have_body_text 'created an issue:' - end - end + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' - describe 'that have been reassigned' do - subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_html_escaped_body_text(assignee.name) + is_expected.to have_body_text(project_issue_path(project, issue)) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + end + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + describe 'that have been relabeled' do + subject { described_class.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) } - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_html_escaped_body_text(assignee.name) - is_expected.to have_body_text(project_issue_path(project, issue)) - end - end + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'an email with a labels subscriptions link in its footer' - describe 'that have been relabeled' do - subject { described_class.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(project_issue_path(project, issue)) end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'an email with a labels subscriptions link in its footer' + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) + context 'with a preferred language' do + before do + Gitlab::I18n.locale = :es end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text('foo, bar, and baz') - is_expected.to have_body_text(project_issue_path(project, issue)) - end + after do + Gitlab::I18n.use_default_locale end - context 'with a preferred language' do - before do - Gitlab::I18n.locale = :es - end - - after do - Gitlab::I18n.use_default_locale - end - - it 'always generates the email using the default language' do - is_expected.to have_body_text('foo, bar, and baz') - end + it 'always generates the email using the default language' do + is_expected.to have_body_text('foo, bar, and baz') end end + end - describe 'status changed' do - let(:status) { 'closed' } - subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } + describe 'status changed' do + let(:status) { 'closed' } + subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(project_issue_path project, issue) - end + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) + is_expected.to have_body_text(project_issue_path project, issue) end end + end - describe 'moved to another project' do - let(:new_issue) { create(:issue) } - subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } + describe 'moved to another project' do + let(:new_issue) { create(:issue) } + subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' - it 'contains description about action taken' do - is_expected.to have_body_text 'Issue was moved to another project' - end + it 'contains description about action taken' do + is_expected.to have_body_text 'Issue was moved to another project' + end - it 'has the correct subject and body' do - new_issue_url = project_issue_path(new_issue.project, new_issue) + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(new_issue_url) - is_expected.to have_body_text(project_issue_path(project, issue)) - end + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) end end end + end - context 'for merge requests' do - describe 'that are new' do - subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + context 'for merge requests' do + describe 'that are new' do + subject { described_class.new_merge_request_email(merge_request.assignee_id, merge_request.id) } + + it_behaves_like 'an assignee email' + it_behaves_like 'an email starting a new thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' - it_behaves_like 'an assignee email' - it_behaves_like 'an email starting a new thread with reply-by-email enabled' do - let(:model) { merge_request } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_body_text(merge_request.source_branch) + is_expected.to have_body_text(merge_request.target_branch) end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' - - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request) - is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - is_expected.to have_body_text(merge_request.source_branch) - is_expected.to have_body_text(merge_request.target_branch) - end + end + + it 'contains the description' do + is_expected.to have_html_escaped_body_text merge_request.description + end + + context 'when enabled email_author_in_body' do + before do + stub_application_setting(email_author_in_body: true) end - it 'contains the description' do - is_expected.to have_html_escaped_body_text merge_request.description + it 'contains a link to note author' do + is_expected.to have_html_escaped_body_text merge_request.author_name + is_expected.to have_body_text 'created a merge request:' end + end + end - context 'when enabled email_author_in_body' do - before do - stub_application_setting(email_author_in_body: true) - end + describe 'that are reassigned' do + let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } - it 'contains a link to note author' do - is_expected.to have_html_escaped_body_text merge_request.author_name - is_expected.to have_body_text 'created a merge request:' - end - end + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like "an unsubscribeable thread" - describe 'that are reassigned' do - subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, previous_assignee.id, current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_html_escaped_body_text(previous_assignee.name) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_html_escaped_body_text(assignee.name) end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like "an unsubscribeable thread" + end + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + describe 'that have been relabeled' do + subject { described_class.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) } - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_html_escaped_body_text(previous_assignee.name) - is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - is_expected.to have_html_escaped_body_text(assignee.name) - end - end + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'a user cannot unsubscribe through footer link' + it_behaves_like 'an email with a labels subscriptions link in its footer' - describe 'that have been relabeled' do - subject { described_class.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) } + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'a user cannot unsubscribe through footer link' - it_behaves_like 'an email with a labels subscriptions link in its footer' + it 'has the correct subject and body' do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + end + end - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + describe 'status changed' do + let(:status) { 'reopened' } + subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } - it 'has the correct subject and body' do + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has the correct subject and body' do + aggregate_failures do is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text('foo, bar, and baz') + is_expected.to have_body_text(status) + is_expected.to have_html_escaped_body_text(current_user.name) is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end + end - describe 'status changed' do - let(:status) { 'reopened' } - subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } + describe 'that are merged' do + let(:merge_author) { create(:user) } + subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end + it 'is sent as the merge author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(merge_author.name) + expect(sender.address).to eq(gitlab_sender) + end - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text(status) - is_expected.to have_html_escaped_body_text(current_user.name) - is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - end + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text('merged') + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) end end + end + end - describe 'that are merged' do - let(:merge_author) { create(:user) } - subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } + context 'for snippet notes' do + let(:project_snippet) { create(:project_snippet, project: project) } + let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) } - it_behaves_like 'a multiple recipients email' - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' + subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) } - it 'is sent as the merge author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(merge_author.name) - expect(sender.address).to eq(gitlab_sender) - end + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { project_snippet } + end + it_behaves_like 'a user cannot unsubscribe through footer link' - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text('merged') - is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - end - end - end + it 'has the correct subject and body' do + is_expected.to have_referable_subject(project_snippet, reply: true) + is_expected.to have_html_escaped_body_text project_snippet_note.note end end @@ -1239,4 +1254,18 @@ describe Notify do end end end + + context 'for personal snippet notes' do + let(:personal_snippet) { create(:personal_snippet) } + let(:personal_snippet_note) { create(:note_on_personal_snippet, noteable: personal_snippet) } + + subject { described_class.note_personal_snippet_email(personal_snippet_note.author_id, personal_snippet_note.id) } + + it_behaves_like 'a user cannot unsubscribe through footer link' + + it 'has the correct subject and body' do + is_expected.to have_referable_subject(personal_snippet, reply: true) + is_expected.to have_html_escaped_body_text personal_snippet_note.note + end + end end diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb index 862907c5d01..84c2e9f7e52 100644 --- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb +++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb @@ -2,11 +2,12 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb') describe AddHeadPipelineForEachMergeRequest, :truncate do + include ProjectForksHelper + let(:migration) { described_class.new } let!(:project) { create(:project) } - let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } - let!(:other_project) { forked_project_link.forked_to_project } + let!(:other_project) { fork_project(project) } let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } diff --git a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb new file mode 100644 index 00000000000..b4834705011 --- /dev/null +++ b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20171012125712_migrate_user_authentication_token_to_personal_access_token.rb') + +describe MigrateUserAuthenticationTokenToPersonalAccessToken, :migration do + let(:users) { table(:users) } + let(:personal_access_tokens) { table(:personal_access_tokens) } + + let!(:user) { users.create!(id: 1, email: 'user@example.com', authentication_token: 'user-token', admin: false) } + let!(:admin) { users.create!(id: 2, email: 'admin@example.com', authentication_token: 'admin-token', admin: true) } + + it 'migrates private tokens to Personal Access Tokens' do + migrate! + + expect(personal_access_tokens.count).to eq(2) + + user_token = personal_access_tokens.find_by(user_id: user.id) + admin_token = personal_access_tokens.find_by(user_id: admin.id) + + expect(user_token.token).to eq('user-token') + expect(admin_token.token).to eq('admin-token') + + expect(user_token.scopes).to eq(%w[api].to_yaml) + expect(admin_token.scopes).to eq(%w[api sudo].to_yaml) + end +end diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb index afaa5d836a7..5e16769d63a 100644 --- a/spec/migrations/migrate_user_project_view_spec.rb +++ b/spec/migrations/migrate_user_project_view_spec.rb @@ -5,12 +5,7 @@ require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_proje describe MigrateUserProjectView, :truncate do let(:migration) { described_class.new } - let!(:user) { create(:user) } - - before do - # 0 is the numeric value for the old 'readme' option - user.update_column(:project_view, 0) - end + let!(:user) { create(:user, project_view: 'readme') } describe '#up' do it 'updates project view setting with new value' do diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb new file mode 100644 index 00000000000..262d7742aaf --- /dev/null +++ b/spec/migrations/normalize_ldap_extern_uids_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170921101004_normalize_ldap_extern_uids') + +describe NormalizeLdapExternUids, :migration, :sidekiq do + let!(:identities) { table(:identities) } + + around do |example| + Timecop.freeze { example.run } + end + + before do + stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_BATCH_SIZE", 2) + stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2) + + # LDAP identities + (1..4).each do |i| + identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i) + end + + # Non-LDAP identity + identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]]) + expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]]) + expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]]) + expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(30.seconds.from_now.to_f) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end + + it 'migrates the LDAP identities' do + Sidekiq::Testing.inline! do + migrate! + identities.where(id: 1..4).each do |identity| + expect(identity.extern_uid).to eq("uid=foo #{identity.id},ou=people,dc=example,dc=com") + end + end + end + + it 'does not modify non-LDAP identities' do + Sidekiq::Testing.inline! do + migrate! + identity = identities.last + expect(identity.extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") + end + end +end diff --git a/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb new file mode 100644 index 00000000000..4ea7f441f7c --- /dev/null +++ b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20171026082505_populate_merge_requests_latest_merge_request_diff_id') + +describe PopulateMergeRequestsLatestMergeRequestDiffId, :migration do + let(:projects_table) { table(:projects) } + let(:merge_requests_table) { table(:merge_requests) } + let(:merge_request_diffs_table) { table(:merge_request_diffs) } + + let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') } + + def create_mr!(name, diffs: 0) + merge_request = + merge_requests_table.create!(target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: name, + title: name) + + diffs.times do + merge_request_diffs_table.create!(merge_request_id: merge_request.id) + end + + merge_request + end + + def diffs_for(merge_request) + merge_request_diffs_table.where(merge_request_id: merge_request.id) + end + + describe '#up' do + it 'ignores MRs without diffs' do + merge_request_without_diff = create_mr!('without_diff') + + expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil + + expect { migrate! } + .not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id } + end + + it 'ignores MRs that have a diff ID already set' do + merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3) + diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id) + + merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id) + + expect { migrate! } + .not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id } + end + + it 'migrates multiple MR diffs to the correct values' do + merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) } + + migrate! + + merge_requests.each do |merge_request| + expect(merge_request.reload.latest_merge_request_diff_id) + .to eq(diffs_for(merge_request).maximum(:id)) + end + end + end +end diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb new file mode 100644 index 00000000000..0e884a7d910 --- /dev/null +++ b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys') + +describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do + matcher :be_scheduled_migration do |*expected| + match do |migration| + BackgroundMigrationWorker.jobs.any? do |job| + job['args'] == [migration, expected] + end + end + + failure_message do |migration| + "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!" + end + end + + before do + create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key) + create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key) + # Delete all subkeys so they can be recreated + GpgKeySubkey.destroy_all + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_migration(1) + expect(described_class::MIGRATION).to be_scheduled_migration(2) + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + + it 'schedules background migrations' do + Sidekiq::Testing.inline! do + expect(GpgKeySubkey.count).to eq(0) + + migrate! + + expect(GpgKeySubkey.count).to eq(3) + end + end +end diff --git a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb new file mode 100644 index 00000000000..4ab1bb67058 --- /dev/null +++ b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170926150348_schedule_merge_request_diff_migrations_take_two') + +describe ScheduleMergeRequestDiffMigrationsTakeTwo, :migration, :sidekiq do + matcher :be_scheduled_migration do |time, *expected| + match do |migration| + BackgroundMigrationWorker.jobs.any? do |job| + job['args'] == [migration, expected] && + job['at'].to_i == time.to_i + end + end + + failure_message do |migration| + "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!" + end + end + + let(:merge_request_diffs) { table(:merge_request_diffs) } + let(:merge_requests) { table(:merge_requests) } + let(:projects) { table(:projects) } + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + + projects.create!(id: 1, name: 'gitlab', path: 'gitlab') + + merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master') + + merge_request_diffs.create!(id: 1, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: nil) + merge_request_diffs.create!(id: 2, merge_request_id: 1, st_commits: nil, st_diffs: YAML.dump([])) + merge_request_diffs.create!(id: 3, merge_request_id: 1, st_commits: nil, st_diffs: nil) + merge_request_diffs.create!(id: 4, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: YAML.dump([])) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes.from_now, 1, 1) + expect(described_class::MIGRATION).to be_scheduled_migration(20.minutes.from_now, 2, 2) + expect(described_class::MIGRATION).to be_scheduled_migration(30.minutes.from_now, 4, 4) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end + + it 'migrates the data' do + Sidekiq::Testing.inline! do + non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' + + expect(merge_request_diffs.where(non_empty).count).to eq 3 + + migrate! + + expect(merge_request_diffs.where(non_empty).count).to eq 0 + end + end +end diff --git a/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb b/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb new file mode 100644 index 00000000000..d625b60ff50 --- /dev/null +++ b/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170927112318_update_legacy_diff_notes_type_for_import.rb') + +describe UpdateLegacyDiffNotesTypeForImport, :migration do + let(:notes) { table(:notes) } + + before do + notes.inheritance_column = nil + + notes.create(type: 'Note') + notes.create(type: 'LegacyDiffNote') + notes.create(type: 'Github::Import::Note') + notes.create(type: 'Github::Import::LegacyDiffNote') + end + + it 'updates the notes type' do + migrate! + + expect(notes.pluck(:type)) + .to contain_exactly('Note', 'Github::Import::Note', 'LegacyDiffNote', 'LegacyDiffNote') + end +end diff --git a/spec/migrations/update_notes_type_for_import_spec.rb b/spec/migrations/update_notes_type_for_import_spec.rb new file mode 100644 index 00000000000..06195d970d8 --- /dev/null +++ b/spec/migrations/update_notes_type_for_import_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170927112319_update_notes_type_for_import.rb') + +describe UpdateNotesTypeForImport, :migration do + let(:notes) { table(:notes) } + + before do + notes.inheritance_column = nil + + notes.create(type: 'Note') + notes.create(type: 'LegacyDiffNote') + notes.create(type: 'Github::Import::Note') + notes.create(type: 'Github::Import::LegacyDiffNote') + end + + it 'updates the notes type' do + migrate! + + expect(notes.pluck(:type)) + .to contain_exactly('Note', 'Note', 'LegacyDiffNote', 'Github::Import::LegacyDiffNote') + end +end diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb index 0a45c5ea32d..d4a1553fb0e 100644 --- a/spec/migrations/update_upload_paths_to_system_spec.rb +++ b/spec/migrations/update_upload_paths_to_system_spec.rb @@ -31,7 +31,7 @@ describe UpdateUploadPathsToSystem do end end - describe "#up", truncate: true do + describe "#up", :truncate do it "updates old upload records to the new path" do old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg") @@ -41,7 +41,7 @@ describe UpdateUploadPathsToSystem do end end - describe "#down", truncate: true do + describe "#down", :truncate do it "updates the new system patsh to the old paths" do new_upload = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg") diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 78cacf9ff5d..47b7150d36f 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -114,6 +114,30 @@ describe ApplicationSetting do it { expect(setting.repository_storages).to eq(['default']) } end + context 'circuitbreaker settings' do + [:circuitbreaker_backoff_threshold, + :circuitbreaker_failure_count_threshold, + :circuitbreaker_failure_wait_time, + :circuitbreaker_failure_reset_time, + :circuitbreaker_storage_timeout].each do |field| + it "Validates #{field} as number" do + is_expected.to validate_numericality_of(field) + .only_integer + .is_greater_than_or_equal_to(0) + end + end + + it 'requires the `backoff_threshold` to be lower than the `failure_count_threshold`' do + setting.circuitbreaker_failure_count_threshold = 10 + setting.circuitbreaker_backoff_threshold = 15 + failure_message = "The circuitbreaker backoff threshold should be lower "\ + "than the failure count threshold" + + expect(setting).not_to be_valid + expect(setting.errors[:circuitbreaker_backoff_threshold]).to include(failure_message) + end + end + context 'repository storages' do before do storages = { @@ -207,6 +231,31 @@ describe ApplicationSetting do expect(described_class.current).to eq(:last) end end + + context 'when an ApplicationSetting is not yet present' do + it 'does not cache nil object' do + # when missing settings a nil object is returned, but not cached + allow(described_class).to receive(:last).and_return(nil).twice + expect(described_class.current).to be_nil + + # when the settings are set the method returns a valid object + allow(described_class).to receive(:last).and_return(:last) + expect(described_class.current).to eq(:last) + + # subsequent calls get everything from cache + expect(described_class.current).to eq(:last) + end + end + end + + context 'restrict creating duplicates' do + before do + described_class.create_from_defaults + end + + it 'raises an record creation violation if already created' do + expect { described_class.create_from_defaults }.to raise_error(ActiveRecord::RecordNotUnique) + end end context 'restricted signup domains' do diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb index 926df21ffda..b9946c0315a 100644 --- a/spec/models/blob_viewer/readme_spec.rb +++ b/spec/models/blob_viewer/readme_spec.rb @@ -37,7 +37,7 @@ describe BlobViewer::Readme do context 'when the wiki is not empty' do before do - WikiPages::CreateService.new(project, project.owner, title: 'home', content: 'Home page').execute + create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: 'Home page' }) end it 'returns nil' do diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb index a10a8af5303..4e72d9d748e 100644 --- a/spec/models/ci/artifact_blob_spec.rb +++ b/spec/models/ci/artifact_blob_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Ci::ArtifactBlob do - let(:build) { create(:ci_build, :artifacts) } + set(:project) { create(:project, :public) } + set(:build) { create(:ci_build, :artifacts, project: project) } let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/another-subdirectory/banana_sample.gif') } subject { described_class.new(entry) } @@ -41,4 +42,50 @@ describe Ci::ArtifactBlob do expect(subject.external_storage).to eq(:build_artifact) end end + + describe '#external_url' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) + end + + context '.gif extension' do + it 'returns nil' do + expect(subject.external_url(build.project, build)).to be_nil + end + end + + context 'txt extensions' do + let(:path) { 'other_artifacts_0.1.2/doc_sample.txt' } + let(:entry) { build.artifacts_metadata_entry(path) } + + it 'returns a URL' do + url = subject.external_url(build.project, build) + + expect(url).not_to be_nil + expect(url).to eq("http://#{project.namespace.path}.#{Gitlab.config.pages.host}/-/#{project.path}/-/jobs/#{build.id}/artifacts/#{path}") + end + end + end + + describe '#external_link?' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) + end + + context 'gif extensions' do + it 'returns false' do + expect(subject.external_link?(build)).to be false + end + end + + context 'txt extensions' do + let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/doc_sample.txt') } + + it 'returns true' do + expect(subject.external_link?(build)).to be true + end + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 451968c7342..5ed2e1ca99a 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -18,6 +18,7 @@ describe Ci::Build do it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } it { is_expected.to have_many(:deployments) } + it { is_expected.to have_many(:trace_sections)} it { is_expected.to validate_presence_of(:ref) } it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } @@ -320,6 +321,17 @@ describe Ci::Build do end end + describe '#parse_trace_sections!' do + it 'calls ExtractSectionsFromBuildTraceService' do + expect(Ci::ExtractSectionsFromBuildTraceService) + .to receive(:new).with(project, build.user).once.and_call_original + expect_any_instance_of(Ci::ExtractSectionsFromBuildTraceService) + .to receive(:execute).with(build).once + + build.parse_trace_sections! + end + end + describe '#trace' do subject { build.trace } @@ -1259,6 +1271,7 @@ describe Ci::Build do { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true }, { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true }, { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, + { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true }, { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true }, { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, @@ -1731,19 +1744,34 @@ describe Ci::Build do end describe 'state transition when build fails' do + let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user) } + + before do + allow(MergeRequests::AddTodoWhenBuildFailsService).to receive(:new).and_return(service) + allow(service).to receive(:close) + end + context 'when build is configured to be retried' do - subject { create(:ci_build, :running, options: { retry: 3 }) } + subject { create(:ci_build, :running, options: { retry: 3 }, project: project, user: user) } - it 'retries builds and assigns a same user to it' do + it 'retries build and assigns the same user to it' do expect(described_class).to receive(:retry) - .with(subject, subject.user) + .with(subject, user) + + subject.drop! + end + + it 'does not try to create a todo' do + project.add_developer(user) + + expect(service).not_to receive(:commit_status_merge_requests) subject.drop! end end context 'when build is not configured to be retried' do - subject { create(:ci_build, :running) } + subject { create(:ci_build, :running, project: project, user: user) } it 'does not retry build' do expect(described_class).not_to receive(:retry) @@ -1758,6 +1786,14 @@ describe Ci::Build do subject.drop! end + + it 'creates a todo' do + project.add_developer(user) + + expect(service).to receive(:commit_status_merge_requests) + + subject.drop! + end end end end diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb new file mode 100644 index 00000000000..386ee6880cb --- /dev/null +++ b/spec/models/ci/build_trace_section_name_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Ci::BuildTraceSectionName, model: true do + subject { build(:ci_build_trace_section_name) } + + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:trace_sections)} + + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } +end diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb new file mode 100644 index 00000000000..541a9a36fb8 --- /dev/null +++ b/spec/models/ci/build_trace_section_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Ci::BuildTraceSection, model: true do + it { is_expected.to belong_to(:build)} + it { is_expected.to belong_to(:project)} + it { is_expected.to belong_to(:section_name)} + + it { is_expected.to validate_presence_of(:section_name) } + it { is_expected.to validate_presence_of(:build) } + it { is_expected.to validate_presence_of(:project) } +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 9c1e460ab20..2c9e7013b77 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -238,7 +238,7 @@ describe Ci::Pipeline, :mailer do describe '#stage_seeds' do let(:pipeline) do - create(:ci_pipeline, config: { rspec: { script: 'rake' } }) + build(:ci_pipeline, config: { rspec: { script: 'rake' } }) end it 'returns preseeded stage seeds object' do @@ -247,6 +247,14 @@ describe Ci::Pipeline, :mailer do end end + describe '#seeds_size' do + let(:pipeline) { build(:ci_pipeline_with_one_job) } + + it 'returns number of jobs in stage seeds' do + expect(pipeline.seeds_size).to eq 1 + end + end + describe '#legacy_stages' do subject { pipeline.legacy_stages } diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb index 2ce78e34b0c..889c243c8d8 100644 --- a/spec/models/ci/pipeline_variable_spec.rb +++ b/spec/models/ci/pipeline_variable_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::PipelineVariable, models: true do +describe Ci::PipelineVariable do subject { build(:ci_pipeline_variable) } it { is_expected.to include_module(HasVariable) } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 2e686e515c5..584dfe9a5c1 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -183,75 +183,42 @@ describe Ci::Runner do end end - context 'when runner is locked' do + context 'when runner is shared' do before do - runner.locked = true + runner.is_shared = true + build.project.runners = [] end - shared_examples 'locked build picker' do - context 'when runner cannot pick untagged jobs' do - before do - runner.run_untagged = false - end + it 'can handle builds' do + expect(runner.can_pick?(build)).to be_truthy + end - it 'cannot handle builds without tags' do - expect(runner.can_pick?(build)).to be_falsey - end + context 'when runner is locked' do + before do + runner.locked = true end - context 'when having runner tags' do - before do - runner.tag_list = %w(bb cc) - end - - it 'cannot handle it for builds without matching tags' do - build.tag_list = ['aa'] - - expect(runner.can_pick?(build)).to be_falsey - end + it 'can handle builds' do + expect(runner.can_pick?(build)).to be_truthy end end + end - context 'when serving the same project' do - it 'can handle it' do + context 'when runner is not shared' do + context 'when runner is assigned to a project' do + it 'can handle builds' do expect(runner.can_pick?(build)).to be_truthy end - - it_behaves_like 'locked build picker' - - context 'when having runner tags' do - before do - runner.tag_list = %w(bb cc) - build.tag_list = ['bb'] - end - - it 'can handle it for matching tags' do - expect(runner.can_pick?(build)).to be_truthy - end - end end - context 'serving a different project' do + context 'when runner is not assigned to a project' do before do - runner.runner_projects.destroy_all + build.project.runners = [] end - it 'cannot handle it' do + it 'cannot handle builds' do expect(runner.can_pick?(build)).to be_falsey end - - it_behaves_like 'locked build picker' - - context 'when having runner tags' do - before do - runner.tag_list = %w(bb cc) - build.tag_list = ['bb'] - end - - it 'cannot handle it for matching tags' do - expect(runner.can_pick?(build)).to be_falsey - end - end end end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 40bbb10eaac..129dfa07f15 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -178,57 +178,59 @@ describe CacheMarkdownField do end end - describe '#refresh_markdown_cache!' do + describe '#refresh_markdown_cache' do before do thing.foo = updated_markdown end - context 'do_update: false' do - it 'fills all html fields' do - thing.refresh_markdown_cache! + it 'fills all html fields' do + thing.refresh_markdown_cache - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + expect(thing.foo_html).to eq(updated_html) + expect(thing.foo_html_changed?).to be_truthy + expect(thing.baz_html_changed?).to be_truthy + end - it 'does not save the result' do - expect(thing).not_to receive(:update_columns) + it 'does not save the result' do + expect(thing).not_to receive(:update_columns) - thing.refresh_markdown_cache! - end + thing.refresh_markdown_cache + end - it 'updates the markdown cache version' do - thing.cached_markdown_version = nil - thing.refresh_markdown_cache! + it 'updates the markdown cache version' do + thing.cached_markdown_version = nil + thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) - end + expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) end + end - context 'do_update: true' do - it 'fills all html fields' do - thing.refresh_markdown_cache!(do_update: true) + describe '#refresh_markdown_cache!' do + before do + thing.foo = updated_markdown + end - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + it 'fills all html fields' do + thing.refresh_markdown_cache! - it 'skips saving if not persisted' do - expect(thing).to receive(:persisted?).and_return(false) - expect(thing).not_to receive(:update_columns) + expect(thing.foo_html).to eq(updated_html) + expect(thing.foo_html_changed?).to be_truthy + expect(thing.baz_html_changed?).to be_truthy + end - thing.refresh_markdown_cache!(do_update: true) - end + it 'skips saving if not persisted' do + expect(thing).to receive(:persisted?).and_return(false) + expect(thing).not_to receive(:update_columns) - it 'saves the changes using #update_columns' do - expect(thing).to receive(:persisted?).and_return(true) - expect(thing).to receive(:update_columns) - .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => CacheMarkdownField::CACHE_VERSION) + thing.refresh_markdown_cache! + end - thing.refresh_markdown_cache!(do_update: true) - end + it 'saves the changes using #update_columns' do + expect(thing).to receive(:persisted?).and_return(true) + expect(thing).to receive(:update_columns) + .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => CacheMarkdownField::CACHE_VERSION) + + thing.refresh_markdown_cache! end end diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb new file mode 100644 index 00000000000..c163fb01a81 --- /dev/null +++ b/spec/models/concerns/group_descendant_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +describe GroupDescendant, :nested_groups do + let(:parent) { create(:group) } + let(:subgroup) { create(:group, parent: parent) } + let(:subsub_group) { create(:group, parent: subgroup) } + + def all_preloaded_groups(*groups) + groups + [parent, subgroup, subsub_group] + end + + context 'for a group' do + describe '#hierarchy' do + it 'only queries once for the ancestors' do + # make sure the subsub_group does not have anything cached + test_group = create(:group, parent: subsub_group).reload + + query_count = ActiveRecord::QueryRecorder.new { test_group.hierarchy }.count + + expect(query_count).to eq(1) + end + + it 'only queries once for the ancestors when a top is given' do + test_group = create(:group, parent: subsub_group).reload + + recorder = ActiveRecord::QueryRecorder.new { test_group.hierarchy(subgroup) } + expect(recorder.count).to eq(1) + end + + it 'builds a hierarchy for a group' do + expected_hierarchy = { parent => { subgroup => subsub_group } } + + expect(subsub_group.hierarchy).to eq(expected_hierarchy) + end + + it 'builds a hierarchy upto a specified parent' do + expected_hierarchy = { subgroup => subsub_group } + + expect(subsub_group.hierarchy(parent)).to eq(expected_hierarchy) + end + + it 'raises an error if specifying a base that is not part of the tree' do + expect { subsub_group.hierarchy(build_stubbed(:group)) } + .to raise_error('specified top is not part of the tree') + end + end + + describe '.build_hierarchy' do + it 'combines hierarchies until the top' do + other_subgroup = create(:group, parent: parent) + other_subsub_group = create(:group, parent: subgroup) + + groups = all_preloaded_groups(other_subgroup, subsub_group, other_subsub_group) + + expected_hierarchy = { parent => [other_subgroup, { subgroup => [subsub_group, other_subsub_group] }] } + + expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy) + end + + it 'combines upto a given parent' do + other_subgroup = create(:group, parent: parent) + other_subsub_group = create(:group, parent: subgroup) + + groups = [other_subgroup, subsub_group, other_subsub_group] + groups << subgroup # Add the parent as if it was preloaded + + expected_hierarchy = [other_subgroup, { subgroup => [subsub_group, other_subsub_group] }] + expect(described_class.build_hierarchy(groups, parent)).to eq(expected_hierarchy) + end + + it 'handles building a tree out of order' do + other_subgroup = create(:group, parent: parent) + other_subgroup2 = create(:group, parent: parent) + other_subsub_group = create(:group, parent: other_subgroup) + + groups = all_preloaded_groups(subsub_group, other_subgroup2, other_subsub_group, other_subgroup) + expected_hierarchy = { parent => [{ subgroup => subsub_group }, other_subgroup2, { other_subgroup => other_subsub_group }] } + + expect(described_class.build_hierarchy(groups)).to eq(expected_hierarchy) + end + + it 'raises an error if not all elements were preloaded' do + expect { described_class.build_hierarchy([subsub_group]) } + .to raise_error('parent was not preloaded') + end + end + end + + context 'for a project' do + let(:project) { create(:project, namespace: subsub_group) } + + describe '#hierarchy' do + it 'builds a hierarchy for a project' do + expected_hierarchy = { parent => { subgroup => { subsub_group => project } } } + + expect(project.hierarchy).to eq(expected_hierarchy) + end + + it 'builds a hierarchy upto a specified parent' do + expected_hierarchy = { subsub_group => project } + + expect(project.hierarchy(subgroup)).to eq(expected_hierarchy) + end + end + + describe '.build_hierarchy' do + it 'combines hierarchies until the top' do + other_project = create(:project, namespace: parent) + other_subgroup_project = create(:project, namespace: subgroup) + + elements = all_preloaded_groups(other_project, subsub_group, other_subgroup_project) + + expected_hierarchy = { parent => [other_project, { subgroup => [subsub_group, other_subgroup_project] }] } + + expect(described_class.build_hierarchy(elements)).to eq(expected_hierarchy) + end + + it 'combines upto a given parent' do + other_project = create(:project, namespace: parent) + other_subgroup_project = create(:project, namespace: subgroup) + + elements = [other_project, subsub_group, other_subgroup_project] + elements << subgroup # Added as if it was preloaded + + expected_hierarchy = [other_project, { subgroup => [subsub_group, other_subgroup_project] }] + + expect(described_class.build_hierarchy(elements, parent)).to eq(expected_hierarchy) + end + + it 'merges to elements in the same hierarchy' do + expected_hierarchy = { parent => subgroup } + + expect(described_class.build_hierarchy([parent, subgroup])).to eq(expected_hierarchy) + end + + it 'merges complex hierarchies' do + project = create(:project, namespace: parent) + sub_project = create(:project, namespace: subgroup) + subsubsub_group = create(:group, parent: subsub_group) + subsub_project = create(:project, namespace: subsub_group) + subsubsub_project = create(:project, namespace: subsubsub_group) + other_subgroup = create(:group, parent: parent) + other_subproject = create(:project, namespace: other_subgroup) + + elements = [project, subsubsub_project, sub_project, other_subproject, subsub_project] + # Add parent groups as if they were preloaded + elements += [other_subgroup, subsubsub_group, subsub_group, subgroup] + + expected_hierarchy = [ + project, + { + subgroup => [ + { subsub_group => [{ subsubsub_group => subsubsub_project }, subsub_project] }, + sub_project + ] + }, + { other_subgroup => other_subproject } + ] + + actual_hierarchy = described_class.build_hierarchy(elements, parent) + + expect(actual_hierarchy).to eq(expected_hierarchy) + end + end + end +end diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index a38f2553eb1..6866b43432c 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -231,6 +231,18 @@ describe HasStatus do end end + describe '.alive' do + subject { CommitStatus.alive } + + %i[running pending created].each do |status| + it_behaves_like 'containing the job', status + end + + %i[failed success].each do |status| + it_behaves_like 'not containing the job', status + end + end + describe '.created_or_pending' do subject { CommitStatus.created_or_pending } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index fb5fb7daaab..ba57301a3c9 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Issuable do let(:issuable_class) { Issue } - let(:issue) { create(:issue) } + let(:issue) { create(:issue, title: 'An issue', description: 'A description') } let(:user) { create(:user) } describe "Associations" do @@ -264,55 +264,75 @@ describe Issuable do end end - describe "#to_hook_data" do - let(:data) { issue.to_hook_data(user) } - let(:project) { issue.project } - - it "returns correct hook data" do - expect(data[:object_kind]).to eq("issue") - expect(data[:user]).to eq(user.hook_attrs) - expect(data[:object_attributes]).to eq(issue.hook_attrs) - expect(data).not_to have_key(:assignee) - end + describe '#to_hook_data' do + context 'labels are updated' do + let(:labels) { create_list(:label, 2) } - context "issue is assigned" do before do - issue.assignees << user + issue.update(labels: [labels[1]]) end - it "returns correct hook data" do - expect(data[:assignees].first).to eq(user.hook_attrs) + it 'delegates to Gitlab::HookData::IssuableBuilder#build' do + builder = double + + expect(Gitlab::HookData::IssuableBuilder) + .to receive(:new).with(issue).and_return(builder) + expect(builder).to receive(:build).with( + user: user, + changes: hash_including( + 'labels' => [[labels[0].hook_attrs], [labels[1].hook_attrs]] + )) + + issue.to_hook_data(user, old_labels: [labels[0]]) end end - context "merge_request is assigned" do - let(:merge_request) { create(:merge_request) } - let(:data) { merge_request.to_hook_data(user) } + context 'issue is assigned' do + let(:user2) { create(:user) } before do - merge_request.update_attribute(:assignee, user) + issue.assignees << user << user2 end - it "returns correct hook data" do - expect(data[:object_attributes]['assignee_id']).to eq(user.id) - expect(data[:assignee]).to eq(user.hook_attrs) + it 'delegates to Gitlab::HookData::IssuableBuilder#build' do + builder = double + + expect(Gitlab::HookData::IssuableBuilder) + .to receive(:new).with(issue).and_return(builder) + expect(builder).to receive(:build).with( + user: user, + changes: hash_including( + 'assignees' => [[user.hook_attrs], [user.hook_attrs, user2.hook_attrs]] + )) + + issue.to_hook_data(user, old_assignees: [user]) end end - context 'issue has labels' do - let(:labels) { [create(:label), create(:label)] } + context 'merge_request is assigned' do + let(:merge_request) { create(:merge_request) } + let(:user2) { create(:user) } before do - issue.update_attribute(:labels, labels) + merge_request.update(assignee: user) + merge_request.update(assignee: user2) end - it 'includes labels in the hook data' do - expect(data[:labels]).to eq(labels.map(&:hook_attrs)) + it 'delegates to Gitlab::HookData::IssuableBuilder#build' do + builder = double + + expect(Gitlab::HookData::IssuableBuilder) + .to receive(:new).with(merge_request).and_return(builder) + expect(builder).to receive(:build).with( + user: user, + changes: hash_including( + 'assignee_id' => [user.id, user2.id], + 'assignee' => [user.hook_attrs, user2.hook_attrs] + )) + + merge_request.to_hook_data(user, old_assignees: [user]) end end - - include_examples 'project hook data' - include_examples 'deprecated repository hook data' end describe '#labels_array' do diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb new file mode 100644 index 00000000000..7a279547a3a --- /dev/null +++ b/spec/models/concerns/loaded_in_group_list_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe LoadedInGroupList do + let(:parent) { create(:group) } + subject(:found_group) { Group.with_selects_for_list.find_by(id: parent.id) } + + describe '.with_selects_for_list' do + it 'includes the preloaded counts for groups' do + create(:group, parent: parent) + create(:project, namespace: parent) + parent.add_developer(create(:user)) + + found_group = Group.with_selects_for_list.find_by(id: parent.id) + + expect(found_group.preloaded_project_count).to eq(1) + expect(found_group.preloaded_subgroup_count).to eq(1) + expect(found_group.preloaded_member_count).to eq(1) + end + + context 'with archived projects' do + it 'counts including archived projects when `true` is passed' do + create(:project, namespace: parent, archived: true) + create(:project, namespace: parent) + + found_group = Group.with_selects_for_list(archived: 'true').find_by(id: parent.id) + + expect(found_group.preloaded_project_count).to eq(2) + end + + it 'counts only archived projects when `only` is passed' do + create_list(:project, 2, namespace: parent, archived: true) + create(:project, namespace: parent) + + found_group = Group.with_selects_for_list(archived: 'only').find_by(id: parent.id) + + expect(found_group.preloaded_project_count).to eq(2) + end + end + end + + describe '#children_count' do + it 'counts groups and projects' do + create(:group, parent: parent) + create(:project, namespace: parent) + + expect(found_group.children_count).to eq(2) + end + end +end diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index b463d12e448..3106207811a 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -12,6 +12,16 @@ describe Group, 'Routable' do it { is_expected.to have_many(:redirect_routes).dependent(:destroy) } end + describe 'GitLab read-only instance' do + it 'does not save route if route is not present' do + group.route.path = '' + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + expect(group).to receive(:update_route).and_call_original + + expect { group.full_path }.to change { Route.count }.by(0) + end + end + describe 'Callbacks' do it 'creates route record on create' do expect(group.route.path).to eq(group.path) @@ -124,6 +134,7 @@ describe Group, 'Routable' do context 'with RequestStore active', :request_store do it 'does not load the route table more than once' do + group.expires_full_path_cache expect(group).to receive(:uncached_full_path).once.and_call_original 3.times { group.full_path } diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb index 28ff8158e0e..45dfb136aea 100644 --- a/spec/models/concerns/subscribable_spec.rb +++ b/spec/models/concerns/subscribable_spec.rb @@ -6,6 +6,12 @@ describe Subscribable, 'Subscribable' do let(:user_1) { create(:user) } describe '#subscribed?' do + context 'without user' do + it 'returns false' do + expect(resource.subscribed?(nil, project)).to be_falsey + end + end + context 'without project' do it 'returns false when no subscription exists' do expect(resource.subscribed?(user_1)).to be_falsey diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 882afeccfc6..dfb83578fce 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do end describe User, 'TokenAuthenticatable' do - let(:token_field) { :authentication_token } + let(:token_field) { :rss_token } it_behaves_like 'TokenAuthenticatable' describe 'ensures authentication token' do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 4aa9ec789a3..da972d2d86a 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe DiffNote do include RepoHelpers - let(:merge_request) { create(:merge_request) } + let!(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } let(:commit) { project.commit(sample_commit.id) } @@ -98,14 +98,14 @@ describe DiffNote do diff_line = subject.diff_line expect(diff_line.added?).to be true - expect(diff_line.new_line).to eq(position.new_line) + expect(diff_line.new_line).to eq(position.formatter.new_line) expect(diff_line.text).to eq("+ vars = {") end end describe "#line_code" do it "returns the correct line code" do - line_code = Gitlab::Diff::LineCode.generate(position.file_path, position.new_line, 15) + line_code = Gitlab::Git.diff_line_code(position.file_path, position.formatter.new_line, 15) expect(subject.line_code).to eq(line_code) end @@ -255,4 +255,38 @@ describe DiffNote do end end end + + describe "image diff notes" do + let(:path) { "files/images/any_image.png" } + + let!(:position) do + Gitlab::Diff::Position.new( + old_path: path, + new_path: path, + width: 10, + height: 10, + x: 1, + y: 1, + diff_refs: merge_request.diff_refs, + position_type: "image" + ) + end + + describe "validations" do + subject { build(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + + it { is_expected.not_to validate_presence_of(:line_code) } + + it "does not validate diff line" do + diff_line = subject.diff_line + + expect(diff_line).to be nil + expect(subject).to be_valid + end + end + + it "returns true for on_image?" do + expect(subject.on_image?).to be_truthy + end + end end diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 1d6fabe48b1..47eb0717c0c 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -11,4 +11,41 @@ describe Email do expect(described_class.new(email: ' inFO@exAMPLe.com ').email) .to eq 'info@example.com' end + + describe '#update_invalid_gpg_signatures' do + let(:user) do + create(:user, email: 'tula.torphy@abshire.ca').tap do |user| + user.skip_reconfirmation! + end + end + let(:user) { create(:user) } + + it 'synchronizes the gpg keys when the email is updated' do + email = user.emails.create(email: 'new@email.com') + + expect(user).to receive(:update_invalid_gpg_signatures) + + email.confirm + end + end + + describe 'scopes' do + let(:user) { create(:user) } + + it 'scopes confirmed emails' do + create(:email, :confirmed, user: user) + create(:email, user: user) + + expect(user.emails.count).to eq 2 + expect(user.emails.confirmed.count).to eq 1 + end + end + + describe 'delegation' do + let(:user) { create(:user) } + + it 'delegates to :user' do + expect(build(:email, user: user).username).to eq user.username + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 25e5d155894..1ce1d595c60 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -18,7 +18,6 @@ describe Environment do it { is_expected.to validate_length_of(:slug).is_at_most(24) } it { is_expected.to validate_length_of(:external_url).is_at_most(255) } - it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) } describe '.order_by_last_deployed_at' do let(:project) { create(:project, :repository) } @@ -547,6 +546,15 @@ describe Environment do expect(environment.slug).to eq(original_slug) end + + it "regenerates the slug if nil" do + environment = build(:environment, slug: nil) + + new_slug = environment.slug + + expect(new_slug).not_to be_nil + expect(environment.slug).to eq(new_slug) + end end describe '#generate_slug' do @@ -575,6 +583,22 @@ describe Environment do end end + describe '#ref_path' do + subject(:environment) do + create(:environment, name: 'staging / review-1') + end + + it 'returns a path that uses the slug and does not have spaces' do + expect(environment.ref_path).to start_with('refs/environments/staging-review-1-') + end + + it "doesn't change when the slug is nil initially" do + environment.slug = nil + + expect(environment.ref_path).to eq(environment.ref_path) + end + end + describe '#external_url_for' do let(:source_path) { 'source/file.html' } let(:sha) { RepoHelpers.sample_commit.id } diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb new file mode 100644 index 00000000000..532ca1fca8c --- /dev/null +++ b/spec/models/fork_network_member_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe ForkNetworkMember do + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:fork_network) } + end +end diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb new file mode 100644 index 00000000000..a43baf1820a --- /dev/null +++ b/spec/models/fork_network_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe ForkNetwork do + include ProjectForksHelper + + describe '#add_root_as_member' do + it 'adds the root project as a member when creating a new root network' do + project = create(:project) + fork_network = described_class.create(root_project: project) + + expect(fork_network.projects).to include(project) + end + end + + describe '#find_fork_in' do + it 'finds all fork of the current network in al collection' do + network = create(:fork_network) + root_project = network.root_project + another_project = fork_project(root_project) + create(:project) + + expect(network.find_forks_in(Project.all)) + .to contain_exactly(another_project, root_project) + end + end + + describe '#merge_requests' do + it 'finds merge requests within the fork network' do + project = create(:project) + forked_project = fork_project(project) + merge_request = create(:merge_request, source_project: forked_project, target_project: project) + + expect(project.fork_network.merge_requests).to include(merge_request) + end + end + + context 'for a deleted project' do + it 'keeps the fork network' do + project = create(:project, :public) + forked = fork_project(project) + project.destroy! + + fork_network = forked.reload.fork_network + + expect(fork_network.projects).to contain_exactly(forked) + expect(fork_network.root_project).to be_nil + end + + it 'allows multiple fork networks where the root project is deleted' do + first_project = create(:project) + second_project = create(:project) + first_fork = fork_project(first_project) + second_fork = fork_project(second_project) + + first_project.destroy + second_project.destroy + + expect(first_fork.fork_network).not_to be_nil + expect(first_fork.fork_network.root_project).to be_nil + expect(second_fork.fork_network).not_to be_nil + expect(second_fork.fork_network.root_project).to be_nil + end + end +end diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 7dbeb4d2e74..32e33e8f42f 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -1,10 +1,11 @@ require 'spec_helper' describe ForkedProjectLink, "add link on fork" do + include ProjectForksHelper + let(:project_from) { create(:project, :repository) } let(:project_to) { fork_project(project_from, user) } let(:user) { create(:user) } - let(:namespace) { user.namespace } before do project_from.add_reporter(user) @@ -64,13 +65,4 @@ describe ForkedProjectLink, "add link on fork" do expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false) end end - - def fork_project(from_project, user) - service = Projects::ForkService.new(from_project, user) - shell = double('gitlab_shell', fork_repository: true) - - allow(service).to receive(:gitlab_shell).and_return(shell) - - service.execute - end end diff --git a/spec/models/gcp/cluster_spec.rb b/spec/models/gcp/cluster_spec.rb new file mode 100644 index 00000000000..8f39fff6394 --- /dev/null +++ b/spec/models/gcp/cluster_spec.rb @@ -0,0 +1,264 @@ +require 'spec_helper' + +describe Gcp::Cluster do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:service) } + + it { is_expected.to validate_presence_of(:gcp_cluster_zone) } + + describe '.enabled' do + subject { described_class.enabled } + + let!(:cluster) { create(:gcp_cluster, enabled: true) } + + before do + create(:gcp_cluster, enabled: false) + end + + it { is_expected.to contain_exactly(cluster) } + end + + describe '.disabled' do + subject { described_class.disabled } + + let!(:cluster) { create(:gcp_cluster, enabled: false) } + + before do + create(:gcp_cluster, enabled: true) + end + + it { is_expected.to contain_exactly(cluster) } + end + + describe '#default_value_for' do + let(:cluster) { described_class.new } + + it { expect(cluster.gcp_cluster_zone).to eq('us-central1-a') } + it { expect(cluster.gcp_cluster_size).to eq(3) } + it { expect(cluster.gcp_machine_type).to eq('n1-standard-4') } + end + + describe '#validates' do + subject { cluster.valid? } + + context 'when validates gcp_project_id' do + let(:cluster) { build(:gcp_cluster, gcp_project_id: gcp_project_id) } + + context 'when valid' do + let(:gcp_project_id) { 'gcp-project-12345' } + + it { is_expected.to be_truthy } + end + + context 'when empty' do + let(:gcp_project_id) { '' } + + it { is_expected.to be_falsey } + end + + context 'when too long' do + let(:gcp_project_id) { 'A' * 64 } + + it { is_expected.to be_falsey } + end + + context 'when includes abnormal character' do + let(:gcp_project_id) { '!!!!!!' } + + it { is_expected.to be_falsey } + end + end + + context 'when validates gcp_cluster_name' do + let(:cluster) { build(:gcp_cluster, gcp_cluster_name: gcp_cluster_name) } + + context 'when valid' do + let(:gcp_cluster_name) { 'test-cluster' } + + it { is_expected.to be_truthy } + end + + context 'when empty' do + let(:gcp_cluster_name) { '' } + + it { is_expected.to be_falsey } + end + + context 'when too long' do + let(:gcp_cluster_name) { 'A' * 64 } + + it { is_expected.to be_falsey } + end + + context 'when includes abnormal character' do + let(:gcp_cluster_name) { '!!!!!!' } + + it { is_expected.to be_falsey } + end + end + + context 'when validates gcp_cluster_size' do + let(:cluster) { build(:gcp_cluster, gcp_cluster_size: gcp_cluster_size) } + + context 'when valid' do + let(:gcp_cluster_size) { 1 } + + it { is_expected.to be_truthy } + end + + context 'when zero' do + let(:gcp_cluster_size) { 0 } + + it { is_expected.to be_falsey } + end + end + + context 'when validates project_namespace' do + let(:cluster) { build(:gcp_cluster, project_namespace: project_namespace) } + + context 'when valid' do + let(:project_namespace) { 'default-namespace' } + + it { is_expected.to be_truthy } + end + + context 'when empty' do + let(:project_namespace) { '' } + + it { is_expected.to be_truthy } + end + + context 'when too long' do + let(:project_namespace) { 'A' * 64 } + + it { is_expected.to be_falsey } + end + + context 'when includes abnormal character' do + let(:project_namespace) { '!!!!!!' } + + it { is_expected.to be_falsey } + end + end + + context 'when validates restrict_modification' do + let(:cluster) { create(:gcp_cluster) } + + before do + cluster.make_creating! + end + + context 'when created' do + before do + cluster.make_created! + end + + it { is_expected.to be_truthy } + end + + context 'when creating' do + it { is_expected.to be_falsey } + end + end + end + + describe '#state_machine' do + let(:cluster) { build(:gcp_cluster) } + + context 'when transits to created state' do + before do + cluster.gcp_token = 'tmp' + cluster.gcp_operation_id = 'tmp' + cluster.make_created! + end + + it 'nullify gcp_token and gcp_operation_id' do + expect(cluster.gcp_token).to be_nil + expect(cluster.gcp_operation_id).to be_nil + expect(cluster).to be_created + end + end + + context 'when transits to errored state' do + let(:reason) { 'something wrong' } + + before do + cluster.make_errored!(reason) + end + + it 'sets status_reason' do + expect(cluster.status_reason).to eq(reason) + expect(cluster).to be_errored + end + end + end + + describe '#project_namespace_placeholder' do + subject { cluster.project_namespace_placeholder } + + let(:cluster) { create(:gcp_cluster) } + + it 'returns a placeholder' do + is_expected.to eq("#{cluster.project.path}-#{cluster.project.id}") + end + end + + describe '#on_creation?' do + subject { cluster.on_creation? } + + let(:cluster) { create(:gcp_cluster) } + + context 'when status is creating' do + before do + cluster.make_creating! + end + + it { is_expected.to be_truthy } + end + + context 'when status is created' do + before do + cluster.make_created! + end + + it { is_expected.to be_falsey } + end + end + + describe '#api_url' do + subject { cluster.api_url } + + let(:cluster) { create(:gcp_cluster, :created_on_gke) } + let(:api_url) { 'https://' + cluster.endpoint } + + it { is_expected.to eq(api_url) } + end + + describe '#restrict_modification' do + subject { cluster.restrict_modification } + + let(:cluster) { create(:gcp_cluster) } + + context 'when status is created' do + before do + cluster.make_created! + end + + it { is_expected.to be_truthy } + end + + context 'when status is creating' do + before do + cluster.make_creating! + end + + it { is_expected.to be_falsey } + + it 'sets error' do + is_expected.to be_falsey + expect(cluster.errors).not_to be_empty + end + end + end +end diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index fadc8bfeb61..33e6f1de3d1 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' describe GpgKey do describe "associations" do it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:subkeys) } end describe "validation" do @@ -38,6 +39,14 @@ describe GpgKey do expect(gpg_key.primary_keyid).to eq GpgHelpers::User1.primary_keyid end end + + describe 'generate_subkeys' do + it 'extracts the subkeys from the gpg key' do + gpg_key = create(:gpg_key, key: GpgHelpers::User1.public_key_with_extra_signing_key) + + expect(gpg_key.subkeys.count).to eq(2) + end + end end describe '#key=' do @@ -90,11 +99,20 @@ describe GpgKey do it 'email is verified if the user has the matching email' do user = create :user, email: 'bette.cartwright@example.com' gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user + create :email, user: user + user.reload expect(gpg_key.emails_with_verified_status).to eq( 'bette.cartwright@example.com' => true, 'bette.cartwright@example.net' => false ) + + create :email, :confirmed, user: user, email: 'bette.cartwright@example.net' + user.reload + expect(gpg_key.emails_with_verified_status).to eq( + 'bette.cartwright@example.com' => true, + 'bette.cartwright@example.net' => true + ) end end @@ -138,6 +156,14 @@ describe GpgKey do expect(gpg_key.verified?).to be_truthy expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.com')).to be_truthy end + + it 'returns true if one of the email addresses in the key belongs to the user and case-insensitively matches the provided email' do + user = create :user, email: 'bette.cartwright@example.com' + gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user + + expect(gpg_key.verified?).to be_truthy + expect(gpg_key.verified_and_belongs_to_email?('Bette.Cartwright@example.com')).to be_truthy + end end describe '#revoke' do @@ -165,5 +191,29 @@ describe GpgKey do expect(unrelated_gpg_key.destroyed?).to be false end + + it 'deletes all the associated subkeys' do + gpg_key = create :gpg_key, key: GpgHelpers::User3.public_key + + expect(gpg_key.subkeys).to be_present + + gpg_key.revoke + + expect(gpg_key.subkeys(true)).to be_blank + end + + it 'invalidates all signatures associated to the subkeys' do + gpg_key = create :gpg_key, key: GpgHelpers::User3.public_key + gpg_key_subkey = gpg_key.subkeys.last + gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: gpg_key_subkey + + gpg_key.revoke + + expect(gpg_signature.reload).to have_attributes( + verification_status: 'unknown_key', + gpg_key: nil, + gpg_key_subkey: nil + ) + end end end diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb new file mode 100644 index 00000000000..3c86837f47f --- /dev/null +++ b/spec/models/gpg_key_subkey_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe GpgKeySubkey do + subject { build(:gpg_key_subkey) } + + describe 'associations' do + it { is_expected.to belong_to(:gpg_key) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:gpg_key_id) } + it { is_expected.to validate_presence_of(:fingerprint) } + it { is_expected.to validate_presence_of(:keyid) } + end +end diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index c58fd46762a..0136bb61c07 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -1,9 +1,17 @@ require 'rails_helper' RSpec.describe GpgSignature do + let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' } + let!(:project) { create(:project, :repository, path: 'sample-project') } + let!(:commit) { create(:commit, project: project, sha: commit_sha) } + let(:gpg_signature) { create(:gpg_signature, commit_sha: commit_sha) } + let(:gpg_key) { create(:gpg_key) } + let(:gpg_key_subkey) { create(:gpg_key_subkey) } + describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:gpg_key) } + it { is_expected.to belong_to(:gpg_key_subkey) } end describe 'validation' do @@ -15,14 +23,48 @@ RSpec.describe GpgSignature do describe '#commit' do it 'fetches the commit through the project' do - commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' - project = create :project, :repository - commit = create :commit, project: project - gpg_signature = create :gpg_signature, commit_sha: commit_sha - expect_any_instance_of(Project).to receive(:commit).with(commit_sha).and_return(commit) gpg_signature.commit end end + + describe '#gpg_key=' do + it 'supports the assignment of a GpgKey' do + gpg_signature = create(:gpg_signature, gpg_key: gpg_key) + + expect(gpg_signature.gpg_key).to be_an_instance_of(GpgKey) + end + + it 'supports the assignment of a GpgKeySubkey' do + gpg_signature = create(:gpg_signature, gpg_key: gpg_key_subkey) + + expect(gpg_signature.gpg_key).to be_an_instance_of(GpgKeySubkey) + end + + it 'clears gpg_key and gpg_key_subkey_id when passing nil' do + gpg_signature.update_attribute(:gpg_key, nil) + + expect(gpg_signature.gpg_key_id).to be_nil + expect(gpg_signature.gpg_key_subkey_id).to be_nil + end + end + + describe '#gpg_commit' do + context 'when commit does not exist' do + it 'returns nil' do + allow(gpg_signature).to receive(:commit).and_return(nil) + + expect(gpg_signature.gpg_commit).to be_nil + end + end + + context 'when commit exists' do + it 'returns an instance of Gitlab::Gpg::Commit' do + allow(gpg_signature).to receive(:commit).and_return(commit) + + expect(gpg_signature.gpg_commit).to be_an_instance_of(Gitlab::Gpg::Commit) + end + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index f36d6eeb327..0e1a7fdce0b 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -488,6 +488,47 @@ describe Group do end end + describe '#path_changed_hook' do + let(:system_hook_service) { SystemHooksService.new } + + context 'for a new group' do + let(:group) { build(:group) } + + before do + expect(group).to receive(:system_hook_service).and_return(system_hook_service) + end + + it 'does not trigger system hook' do + expect(system_hook_service).to receive(:execute_hooks_for).with(group, :create) + + group.save! + end + end + + context 'for an existing group' do + let(:group) { create(:group, path: 'old-path') } + + context 'when the path is changed' do + let(:new_path) { 'very-new-path' } + + it 'triggers the rename system hook' do + expect(group).to receive(:system_hook_service).and_return(system_hook_service) + expect(system_hook_service).to receive(:execute_hooks_for).with(group, :rename) + + group.update_attributes!(path: new_path) + end + end + + context 'when the path is not changed' do + it 'does not trigger system hook' do + expect(group).not_to receive(:system_hook_service) + + group.update_attributes!(name: 'new name') + end + end + end + end + describe '#secret_variables_for' do let(:project) { create(:project, group: group) } diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 4ca6556d0f4..3ed048744de 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe Identity do +describe Identity do describe 'relations' do it { is_expected.to belong_to(:user) } end @@ -22,4 +22,16 @@ RSpec.describe Identity do expect(other_identity.ldap?).to be_falsey end end + + describe '.with_extern_uid' do + context 'LDAP identity' do + let!(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com') } + + it 'finds the identity when the DN is formatted differently' do + identity = described_class.with_extern_uid('ldapmain', 'uid=John Smith, ou=People, dc=example, dc=com').first + + expect(identity).to eq(ldap_identity) + end + end + end end diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb new file mode 100644 index 00000000000..8548fff5c76 --- /dev/null +++ b/spec/models/instance_configuration_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +RSpec.describe InstanceConfiguration do + context 'without cache' do + describe '#settings' do + describe '#ssh_algorithms_hashes' do + let(:md5) { '54:e0:f8:70:d6:4f:4c:b1:b3:02:44:77:cf:cd:0d:fc' } + let(:sha256) { '9327f0d15a48c4d9f6a3aee65a1825baf9a3412001c98169c5fd022ac27762fc' } + + it 'does not return anything if file does not exist' do + stub_pub_file(exist: false) + + expect(subject.settings[:ssh_algorithms_hashes]).to be_empty + end + + it 'does not return anything if file is empty' do + stub_pub_file + + allow(File).to receive(:read).and_return('') + + expect(subject.settings[:ssh_algorithms_hashes]).to be_empty + end + + it 'returns the md5 and sha256 if file valid and exists' do + stub_pub_file + + result = subject.settings[:ssh_algorithms_hashes].select { |o| o[:md5] == md5 && o[:sha256] == sha256 } + + expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size) + end + + def stub_pub_file(exist: true) + path = 'spec/fixtures/ssh_host_example_key.pub' + path << 'random' unless exist + allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path)) + end + end + + describe '#host' do + it 'returns current instance host' do + allow(Settings.gitlab).to receive(:host).and_return('exampledomain') + + expect(subject.settings[:host]).to eq(Settings.gitlab.host) + end + end + + describe '#gitlab_pages' do + let(:gitlab_pages) { subject.settings[:gitlab_pages] } + it 'returns Settings.pages' do + gitlab_pages.delete(:ip_address) + + expect(gitlab_pages).to eq(Settings.pages.symbolize_keys) + end + + it 'returns the Gitlab\'s pages host ip address' do + expect(gitlab_pages.keys).to include(:ip_address) + end + + it 'returns the ip address as nil if the domain is invalid' do + allow(Settings.pages).to receive(:host).and_return('exampledomain') + + expect(gitlab_pages[:ip_address]).to eq nil + end + + it 'returns the ip address of the domain' do + allow(Settings.pages).to receive(:host).and_return('localhost') + + expect(gitlab_pages[:ip_address]).to eq('127.0.0.1').or eq('::1') + end + end + + describe '#gitlab_ci' do + let(:gitlab_ci) { subject.settings[:gitlab_ci] } + it 'returns Settings.gitalb_ci' do + gitlab_ci.delete(:artifacts_max_size) + + expect(gitlab_ci).to eq(Settings.gitlab_ci.symbolize_keys) + end + + it 'returns the key artifacts_max_size' do + expect(gitlab_ci.keys).to include(:artifacts_max_size) + end + end + end + end + + context 'with cache', :use_clean_rails_memory_store_caching do + it 'caches settings content' do + expect(Rails.cache.read(described_class::CACHE_KEY)).to be_nil + + settings = subject.settings + + expect(Rails.cache.read(described_class::CACHE_KEY)).to eq(settings) + end + + describe 'cached settings' do + before do + subject.settings + end + + it 'expires after EXPIRATION_TIME' do + allow(Time).to receive(:now).and_return(Time.now + described_class::EXPIRATION_TIME) + Rails.cache.cleanup + + expect(Rails.cache.read(described_class::CACHE_KEY)).to eq(nil) + end + end + end +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index e547da0cfbe..bb5033c1628 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -700,18 +700,14 @@ describe Issue do end describe '#hook_attrs' do - let(:attrs_hash) { subject.hook_attrs } + it 'delegates to Gitlab::HookData::IssueBuilder#build' do + builder = double - it 'includes time tracking attrs' do - expect(attrs_hash).to include(:total_time_spent) - expect(attrs_hash).to include(:human_time_estimate) - expect(attrs_hash).to include(:human_total_time_spent) - expect(attrs_hash).to include('time_estimate') - end + expect(Gitlab::HookData::IssueBuilder) + .to receive(:new).with(subject).and_return(builder) + expect(builder).to receive(:build) - it 'includes assignee_ids and deprecated assignee_id' do - expect(attrs_hash).to include(:assignee_id) - expect(attrs_hash).to include(:assignee_ids) + subject.hook_attrs end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 8eabc4ca72f..81c2057e175 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -155,5 +155,15 @@ describe Key, :mailer do it 'strips white spaces' do expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key) end + + it 'invalidates the public_key attribute' do + key = build(:key) + + original = key.public_key + key.key = valid_key + + expect(original.key_text).not_to be_nil + expect(key.public_key.key_text).to eq(valid_key) + end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index a07ce05a865..0a017c068ad 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -488,7 +488,7 @@ describe Member do member.accept_invite!(user) end - it "refreshes user's authorized projects", truncate: true do + it "refreshes user's authorized projects", :truncate do project = member.source expect(user.authorized_projects).not_to include(project) @@ -523,7 +523,7 @@ describe Member do end end - describe "destroying a record", truncate: true do + describe "destroying a record", :truncate do it "refreshes user's authorized projects" do project = create(:project, :private) user = create(:user) diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index 9d4a0ecf8c0..7709cf43200 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -2,14 +2,93 @@ require 'rails_helper' describe MergeRequestDiffCommit do let(:merge_request) { create(:merge_request) } - subject { merge_request.commits.first } + let(:project) { merge_request.project } describe '#to_hash' do + subject { merge_request.commits.first } + it 'returns the same results as Commit#to_hash, except for parent_ids' do - commit_from_repo = merge_request.project.repository.commit(subject.sha) + commit_from_repo = project.repository.commit(subject.sha) commit_from_repo_hash = commit_from_repo.to_hash.merge(parent_ids: []) expect(subject.to_hash).to eq(commit_from_repo_hash) end end + + describe '.create_bulk' do + let(:sha_attribute) { Gitlab::Database::ShaAttribute.new } + let(:merge_request_diff_id) { merge_request.merge_request_diff.id } + let(:commits) do + [ + project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e'), + project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + ] + end + let(:rows) do + [ + { + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "authored_date": "2014-02-27T10:01:38.000+01:00".to_time, + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T10:01:38.000+01:00".to_time, + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com", + "merge_request_diff_id": merge_request_diff_id, + "relative_order": 0, + "sha": sha_attribute.type_cast_for_database('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + }, + { + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "authored_date": "2014-02-27T09:57:31.000+01:00".to_time, + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "committed_date": "2014-02-27T09:57:31.000+01:00".to_time, + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dmitriy.zaporozhets@gmail.com", + "merge_request_diff_id": merge_request_diff_id, + "relative_order": 1, + "sha": sha_attribute.type_cast_for_database('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + } + ] + end + + subject { described_class.create_bulk(merge_request_diff_id, commits) } + + it 'inserts the commits into the database en masse' do + expect(Gitlab::Database).to receive(:bulk_insert) + .with(described_class.table_name, rows) + + subject + end + + context 'with dates larger than the DB limit' do + let(:commits) do + # This commit's date is "Sun Aug 17 07:12:55 292278994 +0000" + [project.commit('ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69')] + end + let(:timestamp) { Time.at((1 << 31) - 1) } + let(:rows) do + [{ + "message": "Weird commit date\n", + "authored_date": timestamp, + "author_name": "Alejandro RodrÃguez", + "author_email": "alejorro70@gmail.com", + "committed_date": timestamp, + "committer_name": "Alejandro RodrÃguez", + "committer_email": "alejorro70@gmail.com", + "merge_request_diff_id": merge_request_diff_id, + "relative_order": 0, + "sha": sha_attribute.type_cast_for_database('ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69') + }] + end + + it 'uses a sanitized date' do + expect(Gitlab::Database).to receive(:bulk_insert) + .with(described_class.table_name, rows) + + subject + end + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d80d5657c42..476a2697605 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe MergeRequest do include RepoHelpers + include ProjectForksHelper subject { create(:merge_request) } @@ -49,6 +50,19 @@ describe MergeRequest do expect(subject).to be_valid end end + + context 'for forks' do + let(:project) { create(:project) } + let(:fork1) { fork_project(project) } + let(:fork2) { fork_project(project) } + + it 'allows merge requests for sibling-forks' do + subject.source_project = fork1 + subject.target_project = fork2 + + expect(subject).to be_valid + end + end end describe 'respond to' do @@ -72,7 +86,7 @@ describe MergeRequest do context 'when the target branch does not exist' do before do - project.repository.raw_repository.delete_branch(subject.target_branch) + project.repository.rm_branch(subject.author, subject.target_branch) end it 'returns nil' do @@ -646,33 +660,21 @@ describe MergeRequest do end end - describe "#hook_attrs" do - let(:attrs_hash) { subject.hook_attrs } + describe '#hook_attrs' do + it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do + builder = double - [:source, :target].each do |key| - describe "#{key} key" do - include_examples 'project hook data', project_key: key do - let(:data) { attrs_hash } - let(:project) { subject.send("#{key}_project") } - end - end - end + expect(Gitlab::HookData::MergeRequestBuilder) + .to receive(:new).with(subject).and_return(builder) + expect(builder).to receive(:build) - it "has all the required keys" do - expect(attrs_hash).to include(:source) - expect(attrs_hash).to include(:target) - expect(attrs_hash).to include(:last_commit) - expect(attrs_hash).to include(:work_in_progress) - expect(attrs_hash).to include(:total_time_spent) - expect(attrs_hash).to include(:human_time_estimate) - expect(attrs_hash).to include(:human_total_time_spent) - expect(attrs_hash).to include('time_estimate') + subject.hook_attrs end end describe '#diverged_commits_count' do let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } + let(:forked_project) { fork_project(project, nil, repository: true) } context 'when the target branch does not exist anymore' do subject { create(:merge_request, source_project: project, target_project: project) } @@ -700,7 +702,7 @@ describe MergeRequest do end context 'diverged on fork' do - subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) } + subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: forked_project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do expect(subject.diverged_commits_count).to eq(29) @@ -708,7 +710,7 @@ describe MergeRequest do end context 'rebased on fork' do - subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) } + subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: forked_project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do expect(subject.diverged_commits_count).to eq(0) @@ -791,6 +793,49 @@ describe MergeRequest do end end + describe '#has_ci?' do + let(:merge_request) { build_stubbed(:merge_request) } + + context 'has ci' do + it 'returns true if MR has head_pipeline_id and commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + allow(merge_request).to receive(:head_pipeline_id) { double } + allow(merge_request).to receive(:has_no_commits?) { false } + + expect(merge_request.has_ci?).to be(true) + end + + it 'returns true if MR has any pipeline and commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + allow(merge_request).to receive(:head_pipeline_id) { nil } + allow(merge_request).to receive(:has_no_commits?) { false } + allow(merge_request).to receive(:all_pipelines) { [double] } + + expect(merge_request.has_ci?).to be(true) + end + + it 'returns true if MR has CI service and commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_service) { double } + allow(merge_request).to receive(:head_pipeline_id) { nil } + allow(merge_request).to receive(:has_no_commits?) { false } + allow(merge_request).to receive(:all_pipelines) { [] } + + expect(merge_request.has_ci?).to be(true) + end + end + + context 'has no ci' do + it 'returns false if MR has no CI service nor pipeline, and no commits' do + allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil } + allow(merge_request).to receive(:head_pipeline_id) { nil } + allow(merge_request).to receive(:all_pipelines) { [] } + allow(merge_request).to receive(:has_no_commits?) { true } + + expect(merge_request.has_ci?).to be(false) + end + end + end + describe '#all_pipelines' do shared_examples 'returning pipelines with proper ordering' do let!(:all_pipelines) do @@ -1214,11 +1259,7 @@ describe MergeRequest do end context 'with environments on source project' do - let(:source_project) do - create(:project, :repository) do |fork_project| - fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) - end - end + let(:source_project) { fork_project(project, nil, repository: true) } let(:merge_request) do create(:merge_request, @@ -1347,7 +1388,7 @@ describe MergeRequest do context 'when the target branch does not exist' do before do - subject.project.repository.raw_repository.delete_branch(subject.target_branch) + subject.project.repository.rm_branch(subject.author, subject.target_branch) end it 'returns nil' do @@ -1382,14 +1423,14 @@ describe MergeRequest do describe "#source_project_missing?" do let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:forked_project) { fork_project(project) } let(:user) { create(:user) } - let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) } context "when the fork exists" do let(:merge_request) do create(:merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end @@ -1403,9 +1444,9 @@ describe MergeRequest do end context "when the fork does not exist" do - let(:merge_request) do + let!(:merge_request) do create(:merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end @@ -1419,23 +1460,49 @@ describe MergeRequest do end describe '#merge_ongoing?' do - it 'returns true when merge_id is present and MR is not merged' do + it 'returns true when the merge request is locked' do + merge_request = build_stubbed(:merge_request, state: :locked) + + expect(merge_request.merge_ongoing?).to be(true) + end + + it 'returns true when merge_id, MR is not merged and it has no running job' do merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') + allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true } expect(merge_request.merge_ongoing?).to be(true) end + + it 'returns false when merge_jid is nil' do + merge_request = build_stubbed(:merge_request, state: :open, merge_jid: nil) + + expect(merge_request.merge_ongoing?).to be(false) + end + + it 'returns false if MR is merged' do + merge_request = build_stubbed(:merge_request, state: :merged, merge_jid: 'foo') + + expect(merge_request.merge_ongoing?).to be(false) + end + + it 'returns false if there is no merge job running' do + merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo') + allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false } + + expect(merge_request.merge_ongoing?).to be(false) + end end describe "#closed_without_fork?" do let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:forked_project) { fork_project(project) } let(:user) { create(:user) } - let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) } context "when the merge request is closed" do let(:closed_merge_request) do create(:closed_merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end @@ -1454,7 +1521,7 @@ describe MergeRequest do context "when the merge request is open" do let(:open_merge_request) do create(:merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end @@ -1473,24 +1540,24 @@ describe MergeRequest do end context 'forked project' do - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } - let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } + let(:forked_project) { fork_project(project, user) } let!(:merge_request) do create(:closed_merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project) end it 'returns false if unforked' do - Projects::UnlinkForkService.new(fork_project, user).execute + Projects::UnlinkForkService.new(forked_project, user).execute expect(merge_request.reload.reopenable?).to be_falsey end it 'returns false if the source project is deleted' do - Projects::DestroyService.new(fork_project, user).execute + Projects::DestroyService.new(forked_project, user).execute expect(merge_request.reload.reopenable?).to be_falsey end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 81d5ab7a6d3..90b768f595e 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' describe Namespace do + include ProjectForksHelper + let!(:namespace) { create(:namespace) } + let(:gitlab_shell) { Gitlab::Shell.new } describe 'associations' do it { is_expected.to have_many :projects } @@ -151,23 +154,32 @@ describe Namespace do end end - describe '#move_dir' do - before do - @namespace = create :namespace - @project = create(:project_empty_repo, namespace: @namespace) - allow(@namespace).to receive(:path_changed?).and_return(true) + describe '#ancestors_upto', :nested_groups do + let(:parent) { create(:group) } + let(:child) { create(:group, parent: parent) } + let(:child2) { create(:group, parent: child) } + + it 'returns all ancestors when no namespace is given' do + expect(child2.ancestors_upto).to contain_exactly(child, parent) + end + + it 'includes ancestors upto but excluding the given ancestor' do + expect(child2.ancestors_upto(parent)).to contain_exactly(child) end + end + + describe '#move_dir', :request_store do + let(:namespace) { create(:namespace) } + let!(:project) { create(:project_empty_repo, namespace: namespace) } it "raises error when directory exists" do - expect { @namespace.move_dir }.to raise_error("namespace directory cannot be moved") + expect { namespace.move_dir }.to raise_error("namespace directory cannot be moved") end it "moves dir if path changed" do - new_path = @namespace.full_path + "_new" - allow(@namespace).to receive(:full_path_was).and_return(@namespace.full_path) - allow(@namespace).to receive(:full_path).and_return(new_path) - expect(@namespace).to receive(:remove_exports!) - expect(@namespace.move_dir).to be_truthy + namespace.update_attributes(path: namespace.full_path + '_new') + + expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy end context "when any project has container images" do @@ -177,14 +189,14 @@ describe Namespace do stub_container_registry_config(enabled: true) stub_container_registry_tags(repository: :any, tags: ['tag']) - create(:project, namespace: @namespace, container_repositories: [container_repository]) + create(:project, namespace: namespace, container_repositories: [container_repository]) - allow(@namespace).to receive(:path_was).and_return(@namespace.path) - allow(@namespace).to receive(:path).and_return('new_path') + allow(namespace).to receive(:path_was).and_return(namespace.path) + allow(namespace).to receive(:path).and_return('new_path') end it 'raises an error about not movable project' do - expect { @namespace.move_dir }.to raise_error(/Namespace cannot be moved/) + expect { namespace.move_dir }.to raise_error(/Namespace cannot be moved/) end end @@ -518,4 +530,25 @@ describe Namespace do end end end + + describe '#has_forks_of?' do + let(:project) { create(:project, :public) } + let!(:forked_project) { fork_project(project, namespace.owner, namespace: namespace) } + + before do + # Reset the fork network relation + project.reload + end + + it 'knows if there is a direct fork in the namespace' do + expect(namespace.find_fork_of(project)).to eq(forked_project) + end + + it 'knows when there is as fork-of-fork in the namespace' do + other_namespace = create(:namespace) + other_fork = fork_project(forked_project, other_namespace.owner, namespace: other_namespace) + + expect(other_namespace.find_fork_of(project)).to eq(other_fork) + end + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index b214074fdce..1ecb50586c7 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -314,6 +314,56 @@ describe Note do expect(subject[active_diff_note1.line_code].first.id).to eq(active_diff_note1.discussion_id) expect(subject[active_diff_note3.line_code].first.id).to eq(active_diff_note3.discussion_id) end + + context 'with image discussions' do + let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images and changes") } + let(:image_path) { "files/images/ee_repo_logo.png" } + let(:text_path) { "bar/branch-test.txt" } + let!(:image_note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position) } + let!(:text_note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: text_position) } + + let(:image_position) do + Gitlab::Diff::Position.new( + old_path: image_path, + new_path: image_path, + width: 100, + height: 100, + x: 1, + y: 1, + position_type: "image", + diff_refs: merge_request2.diff_refs + ) + end + + let(:text_position) do + Gitlab::Diff::Position.new( + old_path: text_path, + new_path: text_path, + old_line: nil, + new_line: 2, + position_type: "text", + diff_refs: merge_request2.diff_refs + ) + end + + it "groups image discussions by file identifier" do + diff_discussion = DiffDiscussion.new([image_note]) + + discussions = merge_request2.notes.grouped_diff_discussions + + expect(discussions.size).to eq(2) + expect(discussions[image_note.diff_file.new_path]).to include(diff_discussion) + end + + it "groups text discussions by line code" do + diff_discussion = DiffDiscussion.new([text_note]) + + discussions = merge_request2.notes.grouped_diff_discussions + + expect(discussions.size).to eq(2) + expect(discussions[text_note.line_code]).to include(diff_discussion) + end + end end context 'diff discussions for older diff refs' do diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index b3513c80150..41e2ab20d69 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -30,7 +30,7 @@ describe ProjectGroupLink do end end - describe "destroying a record", truncate: true do + describe "destroying a record", :truncate do it "refreshes group users' authorized projects" do project = create(:project, :private) group = create(:group) diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index 4bb1db684e6..d37726dc3f1 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -42,7 +42,7 @@ describe ChatMessage::IssueMessage do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '[<http://somewhere.com|project_name>] Issue opened by test.user') + '[<http://somewhere.com|project_name>] Issue opened by Test User (test.user)') expect(subject.attachments).to eq([ { title: "#100 Issue title", @@ -62,7 +62,7 @@ describe ChatMessage::IssueMessage do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by test.user') + '[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by Test User (test.user)') expect(subject.attachments).to be_empty end end @@ -76,10 +76,10 @@ describe ChatMessage::IssueMessage do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '[[project_name](http://somewhere.com)] Issue opened by test.user') + '[[project_name](http://somewhere.com)] Issue opened by Test User (test.user)') expect(subject.attachments).to eq('issue description') expect(subject.activity).to eq({ - title: 'Issue opened by test.user', + title: 'Issue opened by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', text: '[#100 Issue title](http://url.com)', image: 'http://someavatar.com' @@ -95,10 +95,10 @@ describe ChatMessage::IssueMessage do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '[[project_name](http://somewhere.com)] Issue [#100 Issue title](http://url.com) closed by test.user') + '[[project_name](http://somewhere.com)] Issue [#100 Issue title](http://url.com) closed by Test User (test.user)') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ - title: 'Issue closed by test.user', + title: 'Issue closed by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', text: '[#100 Issue title](http://url.com)', image: 'http://someavatar.com' diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index b600a36f578..184a07ae0f9 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -33,7 +33,7 @@ describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') + 'Test User (test.user) opened <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') expect(subject.attachments).to be_empty end end @@ -44,7 +44,7 @@ describe ChatMessage::MergeMessage do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') + 'Test User (test.user) closed <http://somewhere.com/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>: *Merge Request title*') expect(subject.attachments).to be_empty end end @@ -58,10 +58,10 @@ describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') + 'Test User (test.user) opened [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ - title: 'Merge Request opened by test.user', + title: 'Merge Request opened by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', text: '[!100 *Merge Request title*](http://somewhere.com/merge_requests/100)', image: 'http://someavatar.com' @@ -76,10 +76,10 @@ describe ChatMessage::MergeMessage do it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') + 'Test User (test.user) closed [!100 *Merge Request title*](http://somewhere.com/merge_requests/100) in [project_name](http://somewhere.com): *Merge Request title*') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ - title: 'Merge Request closed by test.user', + title: 'Merge Request closed by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', text: '[!100 *Merge Request title*](http://somewhere.com/merge_requests/100)', image: 'http://someavatar.com' diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index a09c2f9935c..5abbd7bec18 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -38,7 +38,7 @@ describe ChatMessage::NoteMessage do context 'without markdown' do it 'returns a message regarding notes on commits' do - expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \ "commit 5f163b2b> in <http://somewhere.com|project_name>: " \ "*Added a commit message*") expect(subject.attachments).to eq([{ @@ -55,11 +55,11 @@ describe ChatMessage::NoteMessage do it 'returns a message regarding notes on commits' do expect(subject.pretext).to eq( - 'test.user [commented on commit 5f163b2b](http://url.com) in [project_name](http://somewhere.com): *Added a commit message*' + 'Test User (test.user) [commented on commit 5f163b2b](http://url.com) in [project_name](http://somewhere.com): *Added a commit message*' ) expect(subject.attachments).to eq('comment on a commit') expect(subject.activity).to eq({ - title: 'test.user [commented on commit 5f163b2b](http://url.com)', + title: 'Test User (test.user) [commented on commit 5f163b2b](http://url.com)', subtitle: 'in [project_name](http://somewhere.com)', text: 'Added a commit message', image: 'http://fakeavatar' @@ -81,7 +81,7 @@ describe ChatMessage::NoteMessage do context 'without markdown' do it 'returns a message regarding notes on a merge request' do - expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \ "merge request !30> in <http://somewhere.com|project_name>: " \ "*merge request title*") expect(subject.attachments).to eq([{ @@ -98,10 +98,10 @@ describe ChatMessage::NoteMessage do it 'returns a message regarding notes on a merge request' do expect(subject.pretext).to eq( - 'test.user [commented on merge request !30](http://url.com) in [project_name](http://somewhere.com): *merge request title*') + 'Test User (test.user) [commented on merge request !30](http://url.com) in [project_name](http://somewhere.com): *merge request title*') expect(subject.attachments).to eq('comment on a merge request') expect(subject.activity).to eq({ - title: 'test.user [commented on merge request !30](http://url.com)', + title: 'Test User (test.user) [commented on merge request !30](http://url.com)', subtitle: 'in [project_name](http://somewhere.com)', text: 'merge request title', image: 'http://fakeavatar' @@ -124,7 +124,7 @@ describe ChatMessage::NoteMessage do context 'without markdown' do it 'returns a message regarding notes on an issue' do expect(subject.pretext).to eq( - "test.user <http://url.com|commented on " \ + "Test User (test.user) <http://url.com|commented on " \ "issue #20> in <http://somewhere.com|project_name>: " \ "*issue title*") expect(subject.attachments).to eq([{ @@ -141,10 +141,10 @@ describe ChatMessage::NoteMessage do it 'returns a message regarding notes on an issue' do expect(subject.pretext).to eq( - 'test.user [commented on issue #20](http://url.com) in [project_name](http://somewhere.com): *issue title*') + 'Test User (test.user) [commented on issue #20](http://url.com) in [project_name](http://somewhere.com): *issue title*') expect(subject.attachments).to eq('comment on an issue') expect(subject.activity).to eq({ - title: 'test.user [commented on issue #20](http://url.com)', + title: 'Test User (test.user) [commented on issue #20](http://url.com)', subtitle: 'in [project_name](http://somewhere.com)', text: 'issue title', image: 'http://fakeavatar' @@ -165,7 +165,7 @@ describe ChatMessage::NoteMessage do context 'without markdown' do it 'returns a message regarding notes on a project snippet' do - expect(subject.pretext).to eq("test.user <http://url.com|commented on " \ + expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \ "snippet $5> in <http://somewhere.com|project_name>: " \ "*snippet title*") expect(subject.attachments).to eq([{ @@ -182,7 +182,7 @@ describe ChatMessage::NoteMessage do it 'returns a message regarding notes on a project snippet' do expect(subject.pretext).to eq( - 'test.user [commented on snippet $5](http://url.com) in [project_name](http://somewhere.com): *snippet title*') + 'Test User (test.user) [commented on snippet $5](http://url.com) in [project_name](http://somewhere.com): *snippet title*') expect(subject.attachments).to eq('comment on a snippet') end end diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index 43b02568cb9..0ff20400999 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe ChatMessage::PipelineMessage do subject { described_class.new(args) } - let(:user) { { name: 'hacker' } } + let(:user) { { name: "The Hacker", username: 'hacker' } } let(:duration) { 7210 } let(:args) do { @@ -22,12 +22,13 @@ describe ChatMessage::PipelineMessage do user: user } end + let(:combined_name) { "The Hacker (hacker)" } context 'without markdown' do context 'pipeline succeeded' do let(:status) { 'success' } let(:color) { 'good' } - let(:message) { build_message('passed') } + let(:message) { build_message('passed', combined_name) } it 'returns a message with information about succeeded build' do expect(subject.pretext).to be_empty @@ -39,7 +40,7 @@ describe ChatMessage::PipelineMessage do context 'pipeline failed' do let(:status) { 'failed' } let(:color) { 'danger' } - let(:message) { build_message } + let(:message) { build_message(status, combined_name) } it 'returns a message with information about failed build' do expect(subject.pretext).to be_empty @@ -75,13 +76,13 @@ describe ChatMessage::PipelineMessage do context 'pipeline succeeded' do let(:status) { 'success' } let(:color) { 'good' } - let(:message) { build_markdown_message('passed') } + let(:message) { build_markdown_message('passed', combined_name) } it 'returns a message with information about succeeded build' do expect(subject.pretext).to be_empty expect(subject.attachments).to eq(message) expect(subject.activity).to eq({ - title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker passed', + title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) passed', subtitle: 'in [project_name](http://example.gitlab.com)', text: 'in 02:00:10', image: '' @@ -92,13 +93,13 @@ describe ChatMessage::PipelineMessage do context 'pipeline failed' do let(:status) { 'failed' } let(:color) { 'danger' } - let(:message) { build_markdown_message } + let(:message) { build_markdown_message(status, combined_name) } it 'returns a message with information about failed build' do expect(subject.pretext).to be_empty expect(subject.attachments).to eq(message) expect(subject.activity).to eq({ - title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker failed', + title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) failed', subtitle: 'in [project_name](http://example.gitlab.com)', text: 'in 02:00:10', image: '' diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index c4adee4f489..7efcba9bcfd 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -29,7 +29,7 @@ describe ChatMessage::WikiPageMessage do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ + 'Test User (test.user) created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ '*Wiki page title*') end end @@ -41,7 +41,7 @@ describe ChatMessage::WikiPageMessage do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ + 'Test User (test.user) edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\ '*Wiki page title*') end end @@ -95,7 +95,7 @@ describe ChatMessage::WikiPageMessage do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') + 'Test User (test.user) created [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') end end @@ -106,7 +106,7 @@ describe ChatMessage::WikiPageMessage do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') + 'Test User (test.user) edited [wiki page](http://url.com) in [project_name](http://somewhere.com): *Wiki page title*') end end end @@ -141,7 +141,7 @@ describe ChatMessage::WikiPageMessage do it 'returns the attachment for a new wiki page' do expect(subject.activity).to eq({ - title: 'test.user created [wiki page](http://url.com)', + title: 'Test User (test.user) created [wiki page](http://url.com)', subtitle: 'in [project_name](http://somewhere.com)', text: 'Wiki page title', image: 'http://someavatar.com' @@ -156,7 +156,7 @@ describe ChatMessage::WikiPageMessage do it 'returns the attachment for an updated wiki page' do expect(subject.activity).to eq({ - title: 'test.user edited [wiki page](http://url.com)', + title: 'Test User (test.user) edited [wiki page](http://url.com)', subtitle: 'in [project_name](http://somewhere.com)', text: 'Wiki page title', image: 'http://someavatar.com' diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 63bf131cfc5..ad22fb2a386 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -24,6 +24,8 @@ describe JiraService do end it { is_expected.not_to validate_presence_of(:url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } end context 'validating urls' do @@ -54,6 +56,18 @@ describe JiraService do expect(service).not_to be_valid end + it 'is not valid when username is missing' do + service.username = nil + + expect(service).not_to be_valid + end + + it 'is not valid when password is missing' do + service.password = nil + + expect(service).not_to be_valid + end + it 'is valid when api url is a valid url' do service.api_url = 'http://jira.test.com/api' diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 537cdadd528..00de536a18b 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -99,45 +99,34 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do describe '#actual_namespace' do subject { service.actual_namespace } - it "returns the default namespace" do - is_expected.to eq(service.send(:default_namespace)) - end - - context 'when namespace is specified' do - before do - service.namespace = 'my-namespace' + shared_examples 'a correctly formatted namespace' do + it 'returns a valid Kubernetes namespace name' do + expect(subject).to match(Gitlab::Regex.kubernetes_namespace_regex) + expect(subject).to eq(expected_namespace) end + end - it "returns the user-namespace" do - is_expected.to eq('my-namespace') - end + it_behaves_like 'a correctly formatted namespace' do + let(:expected_namespace) { service.send(:default_namespace) } end - context 'when service is not assigned to project' do + context 'when the project path contains forbidden characters' do before do - service.project = nil + project.path = '-a_Strange.Path--forSure' end - it "does not return namespace" do - is_expected.to be_nil + it_behaves_like 'a correctly formatted namespace' do + let(:expected_namespace) { "a-strange-path--forsure-#{project.id}" } end end - end - - describe '#actual_namespace' do - subject { service.actual_namespace } - - it "returns the default namespace" do - is_expected.to eq(service.send(:default_namespace)) - end context 'when namespace is specified' do before do service.namespace = 'my-namespace' end - it "returns the user-namespace" do - is_expected.to eq('my-namespace') + it_behaves_like 'a correctly formatted namespace' do + let(:expected_namespace) { 'my-namespace' } end end @@ -146,7 +135,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do service.project = nil end - it "does not return namespace" do + it 'does not return namespace' do is_expected.to be_nil end end @@ -208,7 +197,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do config.dig('users', 0, 'user')['token'] = 'token' config.dig('contexts', 0, 'context')['namespace'] = namespace config.dig('clusters', 0, 'cluster')['certificate-authority-data'] = - Base64.encode64('CA PEM DATA') + Base64.strict_encode64('CA PEM DATA') YAML.dump(config) end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index f89be20ad78..6a5d0decfec 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -108,12 +108,8 @@ describe MicrosoftTeamsService do message: "user created page: Awesome wiki_page" } end - - let(:wiki_page_sample_data) do - service = WikiPages::CreateService.new(project, user, opts) - wiki_page = service.execute - Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') - end + let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: opts) } + let(:wiki_page_sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } it "calls Microsoft Teams API" do chat_service.execute(wiki_page_sample_data) diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb new file mode 100644 index 00000000000..6acee311700 --- /dev/null +++ b/spec/models/project_services/packagist_service_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe PackagistService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + let(:project) { create(:project) } + + let(:packagist_server) { 'https://packagist.example.com' } + let(:packagist_username) { 'theUser' } + let(:packagist_token) { 'verySecret' } + let(:packagist_hook_url) do + "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" + end + + let(:packagist_params) do + { + active: true, + project: project, + properties: { + username: packagist_username, + token: packagist_token, + server: packagist_server + } + } + end + + describe '#execute' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } + let(:packagist_service) { described_class.create(packagist_params) } + + before do + stub_request(:post, packagist_hook_url) + end + + it 'calls Packagist API' do + packagist_service.execute(push_sample_data) + + expect(a_request(:post, packagist_hook_url)).to have_been_made.once + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 78226c6c3fa..0e50909988b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -24,6 +24,7 @@ describe Project do it { is_expected.to have_one(:slack_service) } it { is_expected.to have_one(:microsoft_teams_service) } it { is_expected.to have_one(:mattermost_service) } + it { is_expected.to have_one(:packagist_service) } it { is_expected.to have_one(:pushover_service) } it { is_expected.to have_one(:asana_service) } it { is_expected.to have_many(:boards) } @@ -57,6 +58,7 @@ describe Project do it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:builds) } + it { is_expected.to have_many(:build_trace_section_names)} it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:active_runners) } @@ -76,6 +78,7 @@ describe Project do it { is_expected.to have_many(:uploads).dependent(:destroy) } it { is_expected.to have_many(:pipeline_schedules) } it { is_expected.to have_many(:members_and_requesters) } + it { is_expected.to have_one(:cluster) } context 'after initialized' do it "has a project_feature" do @@ -273,6 +276,12 @@ describe Project do expect(project).to be_valid end + + it 'allows a path ending in a period' do + project = build(:project, path: 'foo.') + + expect(project).to be_valid + end end end @@ -408,21 +417,23 @@ describe Project do end end - describe '#repository_storage_path' do - let(:project) { create(:project, repository_storage: 'custom') } - - before do - FileUtils.mkdir('tmp/tests/custom_repositories') - storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } } - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + describe '#merge_method' do + it 'returns "ff" merge_method when ff is enabled' do + project = build(:project, merge_requests_ff_only_enabled: true) + expect(project.merge_method).to be :ff end - after do - FileUtils.rm_rf('tmp/tests/custom_repositories') + it 'returns "merge" merge_method when ff is disabled' do + project = build(:project, merge_requests_ff_only_enabled: false) + expect(project.merge_method).to be :merge end + end + + describe '#repository_storage_path' do + let(:project) { create(:project) } it 'returns the repository storage path' do - expect(project.repository_storage_path).to eq('tmp/tests/custom_repositories') + expect(Dir.exist?(project.repository_storage_path)).to be(true) end end @@ -689,6 +700,44 @@ describe Project do project.cache_has_external_issue_tracker end.to change { project.has_external_issue_tracker}.to(false) end + + it 'does not cache data when in a read-only GitLab instance' do + allow(Gitlab::Database).to receive(:read_only?) { true } + + expect do + project.cache_has_external_issue_tracker + end.not_to change { project.has_external_issue_tracker } + end + end + + describe '#cache_has_external_wiki' do + let(:project) { create(:project, has_external_wiki: nil) } + + it 'stores true if there is any external_wikis' do + services = double(:service, external_wikis: [ExternalWikiService.new]) + expect(project).to receive(:services).and_return(services) + + expect do + project.cache_has_external_wiki + end.to change { project.has_external_wiki}.to(true) + end + + it 'stores false if there is no external_wikis' do + services = double(:service, external_wikis: []) + expect(project).to receive(:services).and_return(services) + + expect do + project.cache_has_external_wiki + end.to change { project.has_external_wiki}.to(false) + end + + it 'does not cache data when in a read-only GitLab instance' do + allow(Gitlab::Database).to receive(:read_only?) { true } + + expect do + project.cache_has_external_wiki + end.not_to change { project.has_external_wiki } + end end describe '#has_wiki?' do @@ -832,7 +881,7 @@ describe Project do let(:project) { create(:project) } context 'when avatar file is uploaded' do - let(:project) { create(:project, :with_avatar) } + let(:project) { create(:project, :public, :with_avatar) } let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } @@ -1303,7 +1352,7 @@ describe Project do context 'using a regular repository' do it 'creates the repository' do expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.disk_path) + .with(project.repository_storage, project.disk_path) .and_return(true) expect(project.repository).to receive(:after_create) @@ -1313,7 +1362,7 @@ describe Project do it 'adds an error if the repository could not be created' do expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.disk_path) + .with(project.repository_storage, project.disk_path) .and_return(false) expect(project.repository).not_to receive(:after_create) @@ -1370,7 +1419,7 @@ describe Project do .and_return(false) expect(shell).to receive(:add_repository) - .with(project.repository_storage_path, project.disk_path) + .with(project.repository_storage, project.disk_path) .and_return(true) project.ensure_repository @@ -1719,6 +1768,21 @@ describe Project do it { expect(project.gitea_import?).to be true } end + describe '#ancestors_upto', :nested_groups do + let(:parent) { create(:group) } + let(:child) { create(:group, parent: parent) } + let(:child2) { create(:group, parent: child) } + let(:project) { create(:project, namespace: child2) } + + it 'returns all ancestors when no namespace is given' do + expect(project.ancestors_upto).to contain_exactly(child2, child, parent) + end + + it 'includes ancestors upto but excluding the given ancestor' do + expect(project.ancestors_upto(parent)).to contain_exactly(child2, child) + end + end + describe '#lfs_enabled?' do let(:project) { create(:project) } @@ -1814,6 +1878,73 @@ describe Project do end end + context 'forks' do + include ProjectForksHelper + + let(:project) { create(:project, :public) } + let!(:forked_project) { fork_project(project) } + + describe '#fork_network' do + it 'includes a fork of the project' do + expect(project.fork_network.projects).to include(forked_project) + end + + it 'includes a fork of a fork' do + other_fork = fork_project(forked_project) + + expect(project.fork_network.projects).to include(other_fork) + end + + it 'includes sibling forks' do + other_fork = fork_project(project) + + expect(forked_project.fork_network.projects).to include(other_fork) + end + + it 'includes the base project' do + expect(forked_project.fork_network.projects).to include(project.reload) + end + end + + describe '#in_fork_network_of?' do + it 'is true for a real fork' do + expect(forked_project.in_fork_network_of?(project)).to be_truthy + end + + it 'is true for a fork of a fork', :postgresql do + other_fork = fork_project(forked_project) + + expect(other_fork.in_fork_network_of?(project)).to be_truthy + end + + it 'is true for sibling forks' do + sibling = fork_project(project) + + expect(sibling.in_fork_network_of?(forked_project)).to be_truthy + end + + it 'is false when another project is given' do + other_project = build_stubbed(:project) + + expect(forked_project.in_fork_network_of?(other_project)).to be_falsy + end + end + + describe '#fork_source' do + let!(:second_fork) { fork_project(forked_project) } + + it 'returns the direct source if it exists' do + expect(second_fork.fork_source).to eq(forked_project) + end + + it 'returns the root of the fork network when the directs source was deleted' do + forked_project.destroy + + expect(second_fork.fork_source).to eq(project) + end + end + end + describe '#pushes_since_gc' do let(:project) { create(:project) } @@ -2083,6 +2214,12 @@ describe Project do it { expect(project.parent).to eq(project.namespace) } end + describe '#parent_id' do + let(:project) { create(:project) } + + it { expect(project.parent_id).to eq(project.namespace_id) } + end + describe '#parent_changed?' do let(:project) { create(:project) } @@ -2336,6 +2473,7 @@ describe Project do context 'legacy storage' do let(:project) { create(:project, :repository) } let(:gitlab_shell) { Gitlab::Shell.new } + let(:project_storage) { project.send(:storage) } before do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) @@ -2363,12 +2501,24 @@ describe Project do describe '#legacy_storage?' do it 'returns true when storage_version is nil' do - project = build(:project) + project = build(:project, storage_version: nil) + + expect(project.legacy_storage?).to be_truthy + end + + it 'returns true when the storage_version is 0' do + project = build(:project, storage_version: 0) expect(project.legacy_storage?).to be_truthy end end + describe '#hashed_storage?' do + it 'returns false' do + expect(project.hashed_storage?(:repository)).to be_falsey + end + end + describe '#rename_repo' do before do # Project#gitlab_shell returns a new instance of Gitlab::Shell on every @@ -2418,6 +2568,30 @@ describe Project do it { expect { subject }.to raise_error(StandardError) } end + + context 'gitlab pages' do + before do + expect(project_storage).to receive(:rename_repo) { true } + end + + it 'moves pages folder to new location' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + project.rename_repo + end + end + + context 'attachments' do + before do + expect(project_storage).to receive(:rename_repo) { true } + end + + it 'moves uploads folder to new location' do + expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) + + project.rename_repo + end + end end describe '#pages_path' do @@ -2425,6 +2599,38 @@ describe Project do expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path)) end end + + describe '#migrate_to_hashed_storage!' do + it 'returns true' do + expect(project.migrate_to_hashed_storage!).to be_truthy + end + + it 'flags as read-only' do + expect { project.migrate_to_hashed_storage! }.to change { project.repository_read_only }.to(true) + end + + it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do + Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase + + expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in) + + project.migrate_to_hashed_storage! + end + + it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do + Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase + + expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in) + + project.migrate_to_hashed_storage! + end + + it 'schedules ProjectMigrateHashedStorageWorker' do + expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id) + + project.migrate_to_hashed_storage! + end + end end context 'hashed storage' do @@ -2438,6 +2644,24 @@ describe Project do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) end + describe '#legacy_storage?' do + it 'returns false' do + expect(project.legacy_storage?).to be_falsey + end + end + + describe '#hashed_storage?' do + it 'returns true if rolled out' do + expect(project.hashed_storage?(:attachments)).to be_truthy + end + + it 'returns false when not rolled out yet' do + project.storage_version = 1 + + expect(project.hashed_storage?(:attachments)).to be_falsey + end + end + describe '#base_dir' do it 'returns base_dir based on hash of project id' do expect(project.base_dir).to eq('@hashed/6b/86') @@ -2477,10 +2701,6 @@ describe Project do .to receive(:execute_hooks_for) .with(project, :rename) - expect_any_instance_of(Gitlab::UploadsTransfer) - .to receive(:rename_project) - .with('foo', project.path, project.namespace.full_path) - expect(project).to receive(:expire_caches_before_rename) expect(project).to receive(:expires_full_path_cache) @@ -2501,6 +2721,32 @@ describe Project do it { expect { subject }.to raise_error(StandardError) } end + + context 'gitlab pages' do + it 'moves pages folder to new location' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project) + + project.rename_repo + end + end + + context 'attachments' do + it 'keeps uploads folder location unchanged' do + expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project) + + project.rename_repo + end + + context 'when not rolled out' do + let(:project) { create(:project, :repository, storage_version: 1) } + + it 'moves pages folder to new location' do + expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) + + project.rename_repo + end + end + end end describe '#pages_path' do @@ -2508,6 +2754,26 @@ describe Project do expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path)) end end + + describe '#migrate_to_hashed_storage!' do + it 'returns nil' do + expect(project.migrate_to_hashed_storage!).to be_nil + end + + it 'does not flag as read-only' do + expect { project.migrate_to_hashed_storage! }.not_to change { project.repository_read_only } + end + end + end + + describe '#gl_repository' do + let(:project) { create(:project) } + + it 'delegates to Gitlab::GlRepository.gl_repository' do + expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true) + + project.gl_repository(is_wiki: true) + end end describe '#has_ci?' do @@ -2717,6 +2983,17 @@ describe Project do end end + describe '#check_repository_path_availability' do + let(:project) { build(:project) } + + it 'skips gitlab-shell exists?' do + project.skip_disk_validation = true + + expect(project.gitlab_shell).not_to receive(:exists?) + expect(project.check_repository_path_availability).to be_truthy + end + end + describe '#latest_successful_pipeline_for_default_branch' do let(:project) { build(:project) } diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 953df7746eb..3d46434fc27 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -6,13 +6,10 @@ describe ProjectWiki do let(:user) { project.owner } let(:gitlab_shell) { Gitlab::Shell.new } let(:project_wiki) { described_class.new(project, user) } + let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo') } subject { project_wiki } - before do - project_wiki.wiki - end - describe "#path_with_namespace" do it "returns the project path with namespace with the .wiki extension" do expect(subject.path_with_namespace).to eq(project.full_path + '.wiki') @@ -61,8 +58,8 @@ describe ProjectWiki do end describe "#wiki" do - it "contains a Gollum::Wiki instance" do - expect(subject.wiki).to be_a Gollum::Wiki + it "contains a Gitlab::Git::Wiki instance" do + expect(subject.wiki).to be_a Gitlab::Git::Wiki end it "creates a new wiki repo if one does not yet exist" do @@ -70,20 +67,18 @@ describe ProjectWiki do end it "raises CouldNotCreateWikiError if it can't create the wiki repository" do - allow(project_wiki).to receive(:init_repo).and_return(false) - expect { project_wiki.send(:create_repo!) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) + # Create a fresh project which will not have a wiki + project_wiki = described_class.new(create(:project), user) + gitlab_shell = double(:gitlab_shell) + allow(gitlab_shell).to receive(:add_repository) + allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell) + + expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) end end describe "#empty?" do context "when the wiki repository is empty" do - before do - allow_any_instance_of(Gitlab::Shell).to receive(:add_repository) do - create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") - end - allow(project).to receive(:full_path).and_return("non-existant") - end - describe '#empty?' do subject { super().empty? } it { is_expected.to be_truthy } @@ -122,109 +117,128 @@ describe ProjectWiki do end describe "#find_page" do - before do - create_page("index page", "This is an awesome Gollum Wiki") - end + shared_examples 'finding a wiki page' do + before do + create_page("index page", "This is an awesome Gollum Wiki") + end - after do - destroy_page(subject.pages.first.page) - end + after do + destroy_page(subject.pages.first.page) + end - it "returns the latest version of the page if it exists" do - page = subject.find_page("index page") - expect(page.title).to eq("index page") - end + it "returns the latest version of the page if it exists" do + page = subject.find_page("index page") + expect(page.title).to eq("index page") + end + + it "returns nil if the page does not exist" do + expect(subject.find_page("non-existant")).to eq(nil) + end + + it "can find a page by slug" do + page = subject.find_page("index-page") + expect(page.title).to eq("index page") + end - it "returns nil if the page does not exist" do - expect(subject.find_page("non-existant")).to eq(nil) + it "returns a WikiPage instance" do + page = subject.find_page("index page") + expect(page).to be_a WikiPage + end end - it "can find a page by slug" do - page = subject.find_page("index-page") - expect(page.title).to eq("index page") + context 'when Gitaly wiki_find_page is enabled' do + it_behaves_like 'finding a wiki page' end - it "returns a WikiPage instance" do - page = subject.find_page("index page") - expect(page).to be_a WikiPage + context 'when Gitaly wiki_find_page is disabled', :skip_gitaly_mock do + it_behaves_like 'finding a wiki page' end end describe '#find_file' do - before do - file = Gollum::File.new(subject.wiki) - allow_any_instance_of(Gollum::Wiki) - .to receive(:file).with('image.jpg', 'master', true) - .and_return(file) - allow_any_instance_of(Gollum::File) - .to receive(:mime_type) - .and_return('image/jpeg') - allow_any_instance_of(Gollum::Wiki) - .to receive(:file).with('non-existant', 'master', true) - .and_return(nil) - end + shared_examples 'finding a wiki file' do + before do + file = File.open(Rails.root.join('spec', 'fixtures', 'dk.png')) + subject.wiki # Make sure the wiki repo exists - after do - allow_any_instance_of(Gollum::Wiki).to receive(:file).and_call_original - allow_any_instance_of(Gollum::File).to receive(:mime_type).and_call_original - end + BareRepoOperations.new(subject.repository.path_to_repo).commit_file(file, 'image.png') + end + + it 'returns the latest version of the file if it exists' do + file = subject.find_file('image.png') + expect(file.mime_type).to eq('image/png') + end + + it 'returns nil if the page does not exist' do + expect(subject.find_file('non-existant')).to eq(nil) + end - it 'returns the latest version of the file if it exists' do - file = subject.find_file('image.jpg') - expect(file.mime_type).to eq('image/jpeg') + it 'returns a Gitlab::Git::WikiFile instance' do + file = subject.find_file('image.png') + expect(file).to be_a Gitlab::Git::WikiFile + end end - it 'returns nil if the page does not exist' do - expect(subject.find_file('non-existant')).to eq(nil) + context 'when Gitaly wiki_find_file is enabled' do + it_behaves_like 'finding a wiki file' end - it 'returns a Gollum::File instance' do - file = subject.find_file('image.jpg') - expect(file).to be_a Gollum::File + context 'when Gitaly wiki_find_file is disabled', :skip_gitaly_mock do + it_behaves_like 'finding a wiki file' end end describe "#create_page" do - after do - destroy_page(subject.pages.first.page) - end + shared_examples 'creating a wiki page' do + after do + destroy_page(subject.pages.first.page) + end - it "creates a new wiki page" do - expect(subject.create_page("test page", "this is content")).not_to eq(false) - expect(subject.pages.count).to eq(1) - end + it "creates a new wiki page" do + expect(subject.create_page("test page", "this is content")).not_to eq(false) + expect(subject.pages.count).to eq(1) + end - it "returns false when a duplicate page exists" do - subject.create_page("test page", "content") - expect(subject.create_page("test page", "content")).to eq(false) - end + it "returns false when a duplicate page exists" do + subject.create_page("test page", "content") + expect(subject.create_page("test page", "content")).to eq(false) + end - it "stores an error message when a duplicate page exists" do - 2.times { subject.create_page("test page", "content") } - expect(subject.error_message).to match(/Duplicate page:/) - end + it "stores an error message when a duplicate page exists" do + 2.times { subject.create_page("test page", "content") } + expect(subject.error_message).to match(/Duplicate page:/) + end - it "sets the correct commit message" do - subject.create_page("test page", "some content", :markdown, "commit message") - expect(subject.pages.first.page.version.message).to eq("commit message") - end + it "sets the correct commit message" do + subject.create_page("test page", "some content", :markdown, "commit message") + expect(subject.pages.first.page.version.message).to eq("commit message") + end - it 'updates project activity' do - subject.create_page('Test Page', 'This is content') + it 'updates project activity' do + subject.create_page('Test Page', 'This is content') - project.reload + project.reload - expect(project.last_activity_at).to be_within(1.minute).of(Time.now) - expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now) + expect(project.last_activity_at).to be_within(1.minute).of(Time.now) + expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now) + end + end + + context 'when Gitaly wiki_write_page is enabled' do + it_behaves_like 'creating a wiki page' + end + + context 'when Gitaly wiki_write_page is disabled', :skip_gitaly_mock do + it_behaves_like 'creating a wiki page' end end describe "#update_page" do before do create_page("update-page", "some content") - @gollum_page = subject.wiki.paged("update-page") + @gitlab_git_wiki_page = subject.wiki.page(title: "update-page") subject.update_page( - @gollum_page, + @gitlab_git_wiki_page, content: "some other content", format: :markdown, message: "updated page" @@ -246,7 +260,7 @@ describe ProjectWiki do it 'updates project activity' do subject.update_page( - @gollum_page, + @gitlab_git_wiki_page, content: 'Yet more content', format: :markdown, message: 'Updated page again' @@ -260,49 +274,60 @@ describe ProjectWiki do end describe "#delete_page" do - before do - create_page("index", "some content") - @page = subject.wiki.paged("index") - end + shared_examples 'deleting a wiki page' do + before do + create_page("index", "some content") + @page = subject.wiki.page(title: "index") + end - it "deletes the page" do - subject.delete_page(@page) - expect(subject.pages.count).to eq(0) - end + it "deletes the page" do + subject.delete_page(@page) + expect(subject.pages.count).to eq(0) + end - it 'updates project activity' do - subject.delete_page(@page) + it 'updates project activity' do + subject.delete_page(@page) - project.reload + project.reload - expect(project.last_activity_at).to be_within(1.minute).of(Time.now) - expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now) + expect(project.last_activity_at).to be_within(1.minute).of(Time.now) + expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now) + end + end + + context 'when Gitaly wiki_delete_page is enabled' do + it_behaves_like 'deleting a wiki page' + end + + context 'when Gitaly wiki_delete_page is disabled', :skip_gitaly_mock do + it_behaves_like 'deleting a wiki page' end end describe '#create_repo!' do it 'creates a repository' do - expect(subject).to receive(:init_repo) - .with(subject.full_path) - .and_return(true) - + expect(raw_repository.exists?).to eq(false) expect(subject.repository).to receive(:after_create) - expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki) + subject.send(:create_repo!, raw_repository) + + expect(raw_repository.exists?).to eq(true) end end describe '#ensure_repository' do it 'creates the repository if it not exist' do - allow(subject).to receive(:repository_exists?).and_return(false) - - expect(subject).to receive(:create_repo!) + expect(raw_repository.exists?).to eq(false) + expect(subject).to receive(:create_repo!).and_call_original subject.ensure_repository + + expect(raw_repository.exists?).to eq(true) end it 'does not create the repository if it exists' do - allow(subject).to receive(:repository_exists?).and_return(true) + subject.wiki + expect(raw_repository.exists?).to eq(true) expect(subject).not_to receive(:create_repo!) @@ -329,7 +354,7 @@ describe ProjectWiki do end def commit_details - { name: user.name, email: user.email, message: "test commit" } + Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit") end def create_page(name, content) @@ -337,6 +362,6 @@ describe ProjectWiki do end def destroy_page(page) - subject.wiki.delete_page(page, commit_details) + subject.delete_page(page, "test commit") end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 76bb658b10d..8a6aa767ce6 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Repository, models: true do +describe Repository do include RepoHelpers TestBlob = Struct.new(:path) @@ -40,7 +40,7 @@ describe Repository, models: true do it { is_expected.not_to include('feature') } it { is_expected.not_to include('fix') } - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error do broken_repository.branch_names_contains(sample_commit.id) @@ -158,7 +158,7 @@ describe Repository, models: true do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore') @@ -171,7 +171,7 @@ describe Repository, models: true do it_behaves_like 'getting last commit for path' end - context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do + context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do it_behaves_like 'getting last commit for path' end end @@ -192,7 +192,7 @@ describe Repository, models: true do is_expected.to eq('c1acaa5') end - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id @@ -205,7 +205,7 @@ describe Repository, models: true do it_behaves_like 'getting last commit ID for path' end - context 'when Gitaly feature last_commit_for_path is disabled', skip_gitaly_mock: true do + context 'when Gitaly feature last_commit_for_path is disabled', :skip_gitaly_mock do it_behaves_like 'getting last commit ID for path' end end @@ -255,11 +255,11 @@ describe Repository, models: true do it_behaves_like 'finding commits by message' end - context 'when Gitaly commits_by_message feature is disabled', skip_gitaly_mock: true do + context 'when Gitaly commits_by_message feature is disabled', :skip_gitaly_mock do it_behaves_like 'finding commits by message' end - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') } end @@ -299,6 +299,24 @@ describe Repository, models: true do it { is_expected.to be_falsey } end + + context 'when pre-loaded merged branches are provided' do + using RSpec::Parameterized::TableSyntax + + where(:branch, :pre_loaded, :expected) do + 'not-merged-branch' | ['branch-merged'] | false + 'branch-merged' | ['not-merged-branch'] | false + 'branch-merged' | ['branch-merged'] | true + 'not-merged-branch' | ['not-merged-branch'] | false + 'master' | ['master'] | false + end + + with_them do + subject { repository.merged_to_root_ref?(branch, pre_loaded) } + + it { is_expected.to eq(expected) } + end + end end describe '#can_be_merged?' do @@ -589,7 +607,7 @@ describe Repository, models: true do expect(results).to match_array([]) end - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error do broken_repository.search_files_by_content('feature', 'master') @@ -626,7 +644,7 @@ describe Repository, models: true do expect(results).to match_array([]) end - describe 'when storage is broken', broken_storage: true do + describe 'when storage is broken', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') } end @@ -634,20 +652,24 @@ describe Repository, models: true do end describe '#fetch_ref' do - describe 'when storage is broken', broken_storage: true do - it 'should raise a storage error' do - path = broken_repository.path_to_repo + # Setting the var here, sidesteps the stub that makes gitaly raise an error + # before the actual test call + set(:broken_repository) { create(:project, :broken_storage).repository } - expect_to_raise_storage_error { broken_repository.fetch_ref(path, '1', '2') } + describe 'when storage is broken', :broken_storage do + it 'should raise a storage error' do + expect_to_raise_storage_error do + broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2') + end end end end describe '#create_ref' do - it 'redirects the call to fetch_ref' do + it 'redirects the call to write_ref' do ref, ref_path = '1', '2' - expect(repository).to receive(:fetch_ref).with(repository.path_to_repo, ref, ref_path) + expect(repository.raw_repository).to receive(:write_ref).with(ref_path, ref) repository.create_ref(ref, ref_path) end @@ -815,45 +837,70 @@ describe Repository, models: true do end describe '#add_branch' do - context 'when pre hooks were successful' do - it 'runs without errors' do - hook = double(trigger: [true, nil]) - expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + let(:branch_name) { 'new_feature' } + let(:target) { 'master' } - expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error - end + subject { repository.add_branch(user, branch_name, target) } - it 'creates the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + context 'with Gitaly enabled' do + it "calls Gitaly's OperationService" do + expect_any_instance_of(Gitlab::GitalyClient::OperationService) + .to receive(:user_create_branch).with(branch_name, user, target) + .and_return(nil) - branch = repository.add_branch(user, 'new_feature', 'master') + subject + end - expect(branch.name).to eq('new_feature') + it 'creates_the_branch' do + expect(subject.name).to eq(branch_name) + expect(repository.find_branch(branch_name)).not_to be_nil end - it 'calls the after_create_branch hook' do - expect(repository).to receive(:after_create_branch) + context 'with a non-existing target' do + let(:target) { 'fake-target' } - repository.add_branch(user, 'new_feature', 'master') + it "returns false and doesn't create the branch" do + expect(subject).to be(false) + expect(repository.find_branch(branch_name)).to be_nil + end end end - context 'when pre hooks failed' do - it 'gets an error' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) + context 'with Gitaly disabled', :skip_gitaly_mock do + context 'when pre hooks were successful' do + it 'runs without errors' do + hook = double(trigger: [true, nil]) + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect do - repository.add_branch(user, 'new_feature', 'master') - end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) + expect { subject }.not_to raise_error + end + + it 'creates the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + + expect(subject.name).to eq(branch_name) + end + + it 'calls the after_create_branch hook' do + expect(repository).to receive(:after_create_branch) + + subject + end end - it 'does not create the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) + context 'when pre hooks failed' do + it 'gets an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) - expect do - repository.add_branch(user, 'new_feature', 'master') - end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) - expect(repository.find_branch('new_feature')).to be_nil + expect { subject }.to raise_error(Gitlab::Git::HooksService::PreReceiveError) + end + + it 'does not create the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) + + expect { subject }.to raise_error(Gitlab::Git::HooksService::PreReceiveError) + expect(repository.find_branch(branch_name)).to be_nil + end end end end @@ -876,47 +923,6 @@ describe Repository, models: true do end end - describe '#rm_branch' do - let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature - let(:blank_sha) { '0000000000000000000000000000000000000000' } - - context 'when pre hooks were successful' do - it 'runs without errors' do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) - .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature') - - expect { repository.rm_branch(user, 'feature') }.not_to raise_error - end - - it 'deletes the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) - - expect { repository.rm_branch(user, 'feature') }.not_to raise_error - - expect(repository.find_branch('feature')).to be_nil - end - end - - context 'when pre hooks failed' do - it 'gets an error' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) - - expect do - repository.rm_branch(user, 'feature') - end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) - end - - it 'does not delete the branch' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) - - expect do - repository.rm_branch(user, 'feature') - end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) - expect(repository.find_branch('feature')).not_to be_nil - end - end - end - describe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev @@ -1113,7 +1119,7 @@ describe Repository, models: true do expect(repository.exists?).to eq(false) end - context 'with broken storage', broken_storage: true do + context 'with broken storage', :broken_storage do it 'should raise a storage error' do expect_to_raise_storage_error { broken_repository.exists? } end @@ -1125,27 +1131,37 @@ describe Repository, models: true do it_behaves_like 'repo exists check' end - context 'when repository_exists is enabled', skip_gitaly_mock: true do + context 'when repository_exists is enabled', :skip_gitaly_mock do it_behaves_like 'repo exists check' end end describe '#has_visible_content?' do - subject { repository.has_visible_content? } + before do + # If raw_repository.has_visible_content? gets called more than once then + # caching is broken. We don't want that. + expect(repository.raw_repository).to receive(:has_visible_content?) + .once + .and_return(result) + end - describe 'when there are no branches' do - before do - allow(repository.raw_repository).to receive(:branch_count).and_return(0) - end + context 'when true' do + let(:result) { true } - it { is_expected.to eq(false) } + it 'returns true and caches it' do + expect(repository.has_visible_content?).to eq(true) + # Second call hits the cache + expect(repository.has_visible_content?).to eq(true) + end end - describe 'when there are branches' do - it 'returns true' do - expect(repository.raw_repository).to receive(:branch_count).and_return(3) + context 'when false' do + let(:result) { false } - expect(subject).to eq(true) + it 'returns false and caches it' do + expect(repository.has_visible_content?).to eq(false) + # Second call hits the cache + expect(repository.has_visible_content?).to eq(false) end end end @@ -1262,6 +1278,7 @@ describe Repository, models: true do allow(repository).to receive(:empty?).and_return(true) expect(cache).to receive(:expire).with(:empty?) + expect(cache).to receive(:expire).with(:has_visible_content?) repository.expire_emptiness_caches end @@ -1270,6 +1287,7 @@ describe Repository, models: true do allow(repository).to receive(:empty?).and_return(false) expect(cache).not_to receive(:expire).with(:empty?) + expect(cache).not_to receive(:expire).with(:has_visible_content?) repository.expire_emptiness_caches end @@ -1286,21 +1304,31 @@ describe Repository, models: true do let(:message) { 'Test \r\n\r\n message' } - it 'merges the code and returns the commit id' do - expect(merge_commit).to be_present - expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present - end + shared_examples '#merge' do + it 'merges the code and returns the commit id' do + expect(merge_commit).to be_present + expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present + end - it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do - merge_commit_id = merge(repository, user, merge_request, message) + it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do + merge_commit_id = merge(repository, user, merge_request, message) - expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) + expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) + end + + it 'removes carriage returns from commit message' do + merge_commit_id = merge(repository, user, merge_request, message) + + expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r")) + end end - it 'removes carriage returns from commit message' do - merge_commit_id = merge(repository, user, merge_request, message) + context 'with gitaly' do + it_behaves_like '#merge' + end - expect(repository.commit(merge_commit_id).message).to eq(message.delete("\r")) + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#merge' end def merge(repository, user, merge_request, message) @@ -1308,6 +1336,34 @@ describe Repository, models: true do end end + describe '#ff_merge' do + before do + repository.add_branch(user, 'ff-target', 'feature~5') + end + + it 'merges the code and return the commit id' do + merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project) + merge_commit_id = repository.ff_merge(user, + merge_request.diff_head_sha, + merge_request.target_branch, + merge_request: merge_request) + merge_commit = repository.commit(merge_commit_id) + + expect(merge_commit).to be_present + expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present + end + + it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do + merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'ff-target', source_project: project) + merge_commit_id = repository.ff_merge(user, + merge_request.diff_head_sha, + merge_request.target_branch, + merge_request: merge_request) + + expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id) + end + end + describe '#revert' do let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') } let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') } @@ -1481,7 +1537,9 @@ describe Repository, models: true do :gitignore, :koding, :gitlab_ci, - :avatar + :avatar, + :issue_template, + :merge_request_template ]) repository.after_change_head @@ -1599,7 +1657,7 @@ describe Repository, models: true do describe '#expire_branches_cache' do it 'expires the cache' do expect(repository).to receive(:expire_method_caches) - .with(%i(branch_names branch_count)) + .with(%i(branch_names branch_count has_visible_content?)) .and_call_original repository.expire_branches_cache @@ -1617,27 +1675,41 @@ describe Repository, models: true do end describe '#add_tag' do - context 'with a valid target' do - let(:user) { build_stubbed(:user) } + let(:user) { build_stubbed(:user) } - it 'creates the tag using rugged' do - expect(repository.rugged.tags).to receive(:create) - .with('8.5', repository.commit('master').id, - hash_including(message: 'foo', - tagger: hash_including(name: user.name, email: user.email))) - .and_call_original + shared_examples 'adding tag' do + context 'with a valid target' do + it 'creates the tag' do + repository.add_tag(user, '8.5', 'master', 'foo') - repository.add_tag(user, '8.5', 'master', 'foo') - end + tag = repository.find_tag('8.5') + expect(tag).to be_present + expect(tag.message).to eq('foo') + expect(tag.dereferenced_target.id).to eq(repository.commit('master').id) + end - it 'returns a Gitlab::Git::Tag object' do - tag = repository.add_tag(user, '8.5', 'master', 'foo') + it 'returns a Gitlab::Git::Tag object' do + tag = repository.add_tag(user, '8.5', 'master', 'foo') - expect(tag).to be_a(Gitlab::Git::Tag) + expect(tag).to be_a(Gitlab::Git::Tag) + end end - it 'passes commit SHA to pre-receive and update hooks,\ - and tag SHA to post-receive hook' do + context 'with an invalid target' do + it 'returns false' do + expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false + end + end + end + + context 'when Gitaly operation_user_add_tag feature is enabled' do + it_behaves_like 'adding tag' + end + + context 'when Gitaly operation_user_add_tag feature is disabled', :skip_gitaly_mock do + it_behaves_like 'adding tag' + + it 'passes commit SHA to pre-receive and update hooks and tag SHA to post-receive hook' do pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', project) update_hook = Gitlab::Git::Hook.new('update', project) post_receive_hook = Gitlab::Git::Hook.new('post-receive', project) @@ -1655,39 +1727,105 @@ describe Repository, models: true do tag_sha = tag.target expect(pre_receive_hook).to have_received(:trigger) - .with(anything, anything, commit_sha, anything) + .with(anything, anything, anything, commit_sha, anything) expect(update_hook).to have_received(:trigger) - .with(anything, anything, commit_sha, anything) + .with(anything, anything, anything, commit_sha, anything) expect(post_receive_hook).to have_received(:trigger) - .with(anything, anything, tag_sha, anything) + .with(anything, anything, anything, tag_sha, anything) end end + end - context 'with an invalid target' do - it 'returns false' do - expect(repository.add_tag(user, '8.5', 'bar', 'foo')).to be false + describe '#rm_branch' do + shared_examples "user deleting a branch" do + it 'removes a branch' do + expect(repository).to receive(:before_remove_branch) + expect(repository).to receive(:after_remove_branch) + + repository.rm_branch(user, 'feature') end end - end - describe '#rm_branch' do - let(:user) { create(:user) } + context 'with gitaly enabled' do + it_behaves_like "user deleting a branch" - it 'removes a branch' do - expect(repository).to receive(:before_remove_branch) - expect(repository).to receive(:after_remove_branch) + context 'when pre hooks failed' do + before do + allow_any_instance_of(Gitlab::GitalyClient::OperationService) + .to receive(:user_delete_branch).and_raise(Gitlab::Git::HooksService::PreReceiveError) + end + + it 'gets an error and does not delete the branch' do + expect do + repository.rm_branch(user, 'feature') + end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) - repository.rm_branch(user, 'feature') + expect(repository.find_branch('feature')).not_to be_nil + end + end + end + + context 'with gitaly disabled', :skip_gitaly_mock do + it_behaves_like "user deleting a branch" + + let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature + let(:blank_sha) { '0000000000000000000000000000000000000000' } + + context 'when pre hooks were successful' do + it 'runs without errors' do + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) + .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature') + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + end + + it 'deletes the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + + expect(repository.find_branch('feature')).to be_nil + end + end + + context 'when pre hooks failed' do + it 'gets an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) + + expect do + repository.rm_branch(user, 'feature') + end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) + end + + it 'does not delete the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) + + expect do + repository.rm_branch(user, 'feature') + end.to raise_error(Gitlab::Git::HooksService::PreReceiveError) + expect(repository.find_branch('feature')).not_to be_nil + end + end end end describe '#rm_tag' do - it 'removes a tag' do - expect(repository).to receive(:before_remove_tag) + shared_examples 'removing tag' do + it 'removes a tag' do + expect(repository).to receive(:before_remove_tag) + + repository.rm_tag(build_stubbed(:user), 'v1.1.0') + + expect(repository.find_tag('v1.1.0')).to be_nil + end + end - repository.rm_tag(create(:user), 'v1.1.0') + context 'when Gitaly operation_user_delete_tag feature is enabled' do + it_behaves_like 'removing tag' + end - expect(repository.find_tag('v1.1.0')).to be_nil + context 'when Gitaly operation_user_delete_tag feature is disabled', :skip_gitaly_mock do + it_behaves_like 'removing tag' end end @@ -1860,6 +1998,15 @@ describe Repository, models: true do repository.expire_all_method_caches end + + it 'all cache_method definitions are in the lists of method caches' do + methods = repository.methods.map do |method| + match = /^_uncached_(.*)/.match(method) + match[1].to_sym if match + end.compact + + expect(methods).to match_array(Repository::CACHED_METHODS + Repository::MEMOIZED_CACHED_METHODS) + end end describe '#file_on_head' do @@ -1981,19 +2128,41 @@ describe Repository, models: true do end describe '#cache_method_output', :use_clean_rails_memory_store_caching do + let(:fallback) { 10 } + context 'with a non-existing repository' do - let(:value) do - repository.cache_method_output(:cats, fallback: 10) do - raise Rugged::ReferenceError + let(:project) { create(:project) } # No repository + + subject do + repository.cache_method_output(:cats, fallback: fallback) do + repository.cats_call_stub end end - it 'returns a fallback value' do - expect(value).to eq(10) + it 'returns the fallback value' do + expect(subject).to eq(fallback) + end + + it 'avoids calling the original method' do + expect(repository).not_to receive(:cats_call_stub) + + subject + end + end + + context 'with a method throwing a non-existing-repository error' do + subject do + repository.cache_method_output(:cats, fallback: fallback) do + raise Gitlab::Git::Repository::NoRepository + end + end + + it 'returns the fallback value' do + expect(subject).to eq(fallback) end it 'does not cache the data' do - value + subject expect(repository.instance_variable_defined?(:@cats)).to eq(false) expect(repository.send(:cache).exist?(:cats)).to eq(false) @@ -2109,4 +2278,44 @@ describe Repository, models: true do end end end + + describe 'commit cache' do + set(:project) { create(:project, :repository) } + + it 'caches based on SHA' do + # Gets the commit oid, and warms the cache + oid = project.commit.id + + expect(Gitlab::Git::Commit).not_to receive(:find).once + + project.commit_by(oid: oid) + end + + it 'caches nil values' do + expect(Gitlab::Git::Commit).to receive(:find).once + + project.commit_by(oid: '1' * 40) + project.commit_by(oid: '1' * 40) + end + end + + describe '#raw_repository' do + subject { repository.raw_repository } + + it 'returns a Gitlab::Git::Repository representation of the repository' do + expect(subject).to be_a(Gitlab::Git::Repository) + expect(subject.relative_path).to eq(project.disk_path + '.git') + expect(subject.gl_repository).to eq("project-#{project.id}") + end + + context 'with a wiki repository' do + let(:repository) { project.wiki.repository } + + it 'creates a Gitlab::Git::Repository with the proper attributes' do + expect(subject).to be_a(Gitlab::Git::Repository) + expect(subject.relative_path).to eq(project.disk_path + '.wiki.git') + expect(subject.gl_repository).to eq("wiki-#{project.id}") + end + end + end end diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 8f05deb8b15..5ec04b99957 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -1,6 +1,9 @@ require 'spec_helper' describe SentNotification do + set(:user) { create(:user) } + set(:project) { create(:project) } + describe 'validation' do describe 'note validity' do context "when the project doesn't match the noteable's project" do @@ -34,7 +37,6 @@ describe SentNotification do end describe '.record' do - let(:user) { create(:user) } let(:issue) { create(:issue) } it 'creates a new SentNotification' do @@ -43,7 +45,6 @@ describe SentNotification do end describe '.record_note' do - let(:user) { create(:user) } let(:note) { create(:diff_note_on_merge_request) } it 'creates a new SentNotification' do @@ -51,6 +52,123 @@ describe SentNotification do end end + describe '#unsubscribable?' do + shared_examples 'an unsubscribable notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for #{noteable_type}" do + it { expect(subject).to be_unsubscribable } + end + end + + shared_examples 'a non-unsubscribable notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for a #{noteable_type}" do + it { expect(subject).not_to be_unsubscribable } + end + end + + it_behaves_like 'an unsubscribable notification', 'issue' do + let(:noteable) { create(:issue, project: project) } + end + + it_behaves_like 'an unsubscribable notification', 'merge request' do + let(:noteable) { create(:merge_request, source_project: project) } + end + + it_behaves_like 'a non-unsubscribable notification', 'commit' do + let(:project) { create(:project, :repository) } + let(:noteable) { project.commit } + end + + it_behaves_like 'a non-unsubscribable notification', 'personal snippet' do + let(:noteable) { create(:personal_snippet, project: project) } + end + + it_behaves_like 'a non-unsubscribable notification', 'project snippet' do + let(:noteable) { create(:project_snippet, project: project) } + end + end + + describe '#for_commit?' do + shared_examples 'a commit notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for #{noteable_type}" do + it { expect(subject).to be_for_commit } + end + end + + shared_examples 'a non-commit notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for a #{noteable_type}" do + it { expect(subject).not_to be_for_commit } + end + end + + it_behaves_like 'a non-commit notification', 'issue' do + let(:noteable) { create(:issue, project: project) } + end + + it_behaves_like 'a non-commit notification', 'merge request' do + let(:noteable) { create(:merge_request, source_project: project) } + end + + it_behaves_like 'a commit notification', 'commit' do + let(:project) { create(:project, :repository) } + let(:noteable) { project.commit } + end + + it_behaves_like 'a non-commit notification', 'personal snippet' do + let(:noteable) { create(:personal_snippet, project: project) } + end + + it_behaves_like 'a non-commit notification', 'project snippet' do + let(:noteable) { create(:project_snippet, project: project) } + end + end + + describe '#for_snippet?' do + shared_examples 'a snippet notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for #{noteable_type}" do + it { expect(subject).to be_for_snippet } + end + end + + shared_examples 'a non-snippet notification' do |noteable_type| + subject { described_class.record(noteable, user.id) } + + context "for a #{noteable_type}" do + it { expect(subject).not_to be_for_snippet } + end + end + + it_behaves_like 'a non-snippet notification', 'issue' do + let(:noteable) { create(:issue, project: project) } + end + + it_behaves_like 'a non-snippet notification', 'merge request' do + let(:noteable) { create(:merge_request, source_project: project) } + end + + it_behaves_like 'a non-snippet notification', 'commit' do + let(:project) { create(:project, :repository) } + let(:noteable) { project.commit } + end + + it_behaves_like 'a snippet notification', 'personal snippet' do + let(:noteable) { create(:personal_snippet, project: project) } + end + + it_behaves_like 'a snippet notification', 'project snippet' do + let(:noteable) { create(:project_snippet, project: project) } + end + end + describe '#create_reply' do context 'for issue' do let(:issue) { create(:issue) } diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb new file mode 100644 index 00000000000..37fc3cb64f0 --- /dev/null +++ b/spec/models/user_custom_attribute_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe UserCustomAttribute do + describe 'assocations' do + it { is_expected.to belong_to(:user) } + end + + describe 'validations' do + subject { build :user_custom_attribute } + + it { is_expected.to validate_presence_of(:user_id) } + it { is_expected.to validate_presence_of(:key) } + it { is_expected.to validate_presence_of(:value) } + it { is_expected.to validate_uniqueness_of(:key).scoped_to(:user_id) } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c1affa812aa..d2f97009ad9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe User do include Gitlab::CurrentSettings + include ProjectForksHelper describe 'modules' do subject { described_class } @@ -39,6 +40,7 @@ describe User do it { is_expected.to have_many(:chat_names).dependent(:destroy) } it { is_expected.to have_many(:uploads).dependent(:destroy) } it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') } + it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') } describe "#abuse_report" do let(:current_user) { create(:user) } @@ -344,7 +346,6 @@ describe User do describe "Respond to" do it { is_expected.to respond_to(:admin?) } it { is_expected.to respond_to(:name) } - it { is_expected.to respond_to(:private_token) } it { is_expected.to respond_to(:external?) } end @@ -359,9 +360,22 @@ describe User do expect(external_user.projects_limit).to be 0 end end + + describe '#check_for_verified_email' do + let(:user) { create(:user) } + let(:secondary) { create(:email, :confirmed, email: 'secondary@example.com', user: user) } + + it 'allows a verfied secondary email to be used as the primary without needing reconfirmation' do + user.update_attributes!(email: secondary.email) + user.reload + expect(user.email).to eq secondary.email + expect(user.unconfirmed_email).to eq nil + expect(user.confirmed?).to be_truthy + end + end end - describe 'after update hook' do + describe 'after commit hook' do describe '.update_invalid_gpg_signatures' do let(:user) do create(:user, email: 'tula.torphy@abshire.ca').tap do |user| @@ -375,10 +389,50 @@ describe User do end it 'synchronizes the gpg keys when the email is updated' do - expect(user).to receive(:update_invalid_gpg_signatures) + expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice) user.update_attributes!(email: 'shawnee.ritchie@denesik.com') end end + + describe '#update_emails_with_primary_email' do + before do + @user = create(:user, email: 'primary@example.com').tap do |user| + user.skip_reconfirmation! + end + @secondary = create :email, email: 'secondary@example.com', user: @user + @user.reload + end + + it 'gets called when email updated' do + expect(@user).to receive(:update_emails_with_primary_email) + + @user.update_attributes!(email: 'new_primary@example.com') + end + + it 'adds old primary to secondary emails when secondary is a new email ' do + @user.update_attributes!(email: 'new_primary@example.com') + @user.reload + + expect(@user.emails.count).to eq 2 + expect(@user.emails.pluck(:email)).to match_array([@secondary.email, 'primary@example.com']) + end + + it 'adds old primary to secondary emails if secondary is becoming a primary' do + @user.update_attributes!(email: @secondary.email) + @user.reload + + expect(@user.emails.count).to eq 1 + expect(@user.emails.first.email).to eq 'primary@example.com' + end + + it 'transfers old confirmation values into new secondary' do + @user.update_attributes!(email: @secondary.email) + @user.reload + + expect(@user.emails.count).to eq 1 + expect(@user.emails.first.confirmed_at).not_to eq nil + end + end end describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do @@ -466,20 +520,15 @@ describe User do describe '#generate_password' do it "does not generate password by default" do user = create(:user, password: 'abcdefghe') - expect(user.password).to eq('abcdefghe') - end - end - describe 'authentication token' do - it "has authentication token" do - user = create(:user) - expect(user.authentication_token).not_to be_blank + expect(user.password).to eq('abcdefghe') end end describe 'ensure incoming email token' do it 'has incoming email token' do user = create(:user) + expect(user.incoming_email_token).not_to be_blank end end @@ -522,6 +571,7 @@ describe User do it 'ensures an rss token on read' do user = create(:user, rss_token: nil) rss_token = user.rss_token + expect(rss_token).not_to be_blank expect(user.reload.rss_token).to eq rss_token end @@ -632,6 +682,7 @@ describe User do it "blocks user" do user.block + expect(user.blocked?).to be_truthy end end @@ -737,14 +788,16 @@ describe User do end it "creates external user by default" do - user = build(:user) + user = create(:user) expect(user.external).to be_truthy + expect(user.can_create_group).to be_falsey + expect(user.projects_limit).to be 0 end describe 'with default overrides' do it "creates a non-external user" do - user = build(:user, external: false) + user = create(:user, external: false) expect(user.external).to be_falsey end @@ -965,6 +1018,7 @@ describe User do it 'is case-insensitive' do user = create(:user, username: 'JohnDoe') + expect(described_class.find_by_username('JOHNDOE')).to eq user end end @@ -977,6 +1031,7 @@ describe User do it 'is case-insensitive' do user = create(:user, username: 'JohnDoe') + expect(described_class.find_by_username!('JOHNDOE')).to eq user end end @@ -1066,11 +1121,13 @@ describe User do it 'is true if avatar is image' do user.update_attribute(:avatar, 'uploads/avatar.png') + expect(user.avatar_type).to be_truthy end it 'is false if avatar is html page' do user.update_attribute(:avatar, 'uploads/avatar.html') + expect(user.avatar_type).to eq(['only images allowed']) end end @@ -1093,6 +1150,50 @@ describe User do end end + describe '#all_emails' do + let(:user) { create(:user) } + + it 'returns all emails' do + email_confirmed = create :email, user: user, confirmed_at: Time.now + email_unconfirmed = create :email, user: user + user.reload + + expect(user.all_emails).to match_array([user.email, email_unconfirmed.email, email_confirmed.email]) + end + end + + describe '#verified_emails' do + let(:user) { create(:user) } + + it 'returns only confirmed emails' do + email_confirmed = create :email, user: user, confirmed_at: Time.now + create :email, user: user + user.reload + + expect(user.verified_emails).to match_array([user.email, email_confirmed.email]) + end + end + + describe '#verified_email?' do + let(:user) { create(:user) } + + it 'returns true when the email is verified/confirmed' do + email_confirmed = create :email, user: user, confirmed_at: Time.now + create :email, user: user + user.reload + + expect(user.verified_email?(user.email)).to be_truthy + expect(user.verified_email?(email_confirmed.email.titlecase)).to be_truthy + end + + it 'returns false when the email is not verified/confirmed' do + email_unconfirmed = create :email, user: user + user.reload + + expect(user.verified_email?(email_unconfirmed.email)).to be_falsy + end + end + describe '#requires_ldap_check?' do let(:user) { described_class.new } @@ -1100,6 +1201,7 @@ describe User do # Create a condition which would otherwise cause 'true' to be returned allow(user).to receive(:ldap_user?).and_return(true) user.last_credential_check_at = nil + expect(user.requires_ldap_check?).to be_falsey end @@ -1110,6 +1212,7 @@ describe User do it 'is false for non-LDAP users' do allow(user).to receive(:ldap_user?).and_return(false) + expect(user.requires_ldap_check?).to be_falsey end @@ -1120,11 +1223,13 @@ describe User do it 'is true when the user has never had an LDAP check before' do user.last_credential_check_at = nil + expect(user.requires_ldap_check?).to be_truthy end it 'is true when the last LDAP check happened over 1 hour ago' do user.last_credential_check_at = 2.hours.ago + expect(user.requires_ldap_check?).to be_truthy end end @@ -1135,16 +1240,19 @@ describe User do describe '#ldap_user?' do it 'is true if provider name starts with ldap' do user = create(:omniauth_user, provider: 'ldapmain') + expect(user.ldap_user?).to be_truthy end it 'is false for other providers' do user = create(:omniauth_user, provider: 'other-provider') + expect(user.ldap_user?).to be_falsey end it 'is false if no extern_uid is provided' do user = create(:omniauth_user, extern_uid: nil) + expect(user.ldap_user?).to be_falsey end end @@ -1152,6 +1260,7 @@ describe User do describe '#ldap_identity' do it 'returns ldap identity' do user = create :omniauth_user + expect(user.ldap_identity.provider).not_to be_empty end end @@ -1161,6 +1270,7 @@ describe User do it 'blocks user flaging the action caming from ldap' do user.ldap_block + expect(user.blocked?).to be_truthy expect(user.ldap_blocked?).to be_truthy end @@ -1233,18 +1343,22 @@ describe User do expect(user.starred?(project2)).to be_falsey star1 = UsersStarProject.create!(project: project1, user: user) + expect(user.starred?(project1)).to be_truthy expect(user.starred?(project2)).to be_falsey star2 = UsersStarProject.create!(project: project2, user: user) + expect(user.starred?(project1)).to be_truthy expect(user.starred?(project2)).to be_truthy star1.destroy + expect(user.starred?(project1)).to be_falsey expect(user.starred?(project2)).to be_truthy star2.destroy + expect(user.starred?(project1)).to be_falsey expect(user.starred?(project2)).to be_falsey end @@ -1256,9 +1370,13 @@ describe User do project = create(:project, :public) expect(user.starred?(project)).to be_falsey + user.toggle_star(project) + expect(user.starred?(project)).to be_truthy + user.toggle_star(project) + expect(user.starred?(project)).to be_falsey end end @@ -1307,7 +1425,7 @@ describe User do describe "#contributed_projects" do subject { create(:user) } let!(:project1) { create(:project) } - let!(:project2) { create(:project, forked_from_project: project3) } + let!(:project2) { fork_project(project3) } let!(:project3) { create(:project) } let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } let!(:push_event) { create(:push_event, project: project1, author: subject) } @@ -1331,6 +1449,23 @@ describe User do end end + describe '#fork_of' do + let(:user) { create(:user) } + + it "returns a user's fork of a project" do + project = create(:project, :public) + user_fork = fork_project(project, user, namespace: user.namespace) + + expect(user.fork_of(project)).to eq(user_fork) + end + + it 'returns nil if the project does not have a fork network' do + project = create(:project) + + expect(user.fork_of(project)).to be_nil + end + end + describe '#can_be_removed?' do subject { create(:user) } @@ -1383,7 +1518,7 @@ describe User do it { is_expected.to eq([private_group]) } end - describe '#authorized_projects', truncate: true do + describe '#authorized_projects', :truncate do context 'with a minimum access level' do it 'includes projects for which the user is an owner' do user = create(:user) @@ -1437,9 +1572,11 @@ describe User do user = create(:user) member = group.add_developer(user) + expect(user.authorized_projects).to include(project) member.destroy + expect(user.authorized_projects).not_to include(project) end @@ -1460,9 +1597,11 @@ describe User do project = create(:project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] + expect(user2.authorized_projects).to include(project) project.destroy + expect(user2.authorized_projects).not_to include(project) end @@ -1472,9 +1611,11 @@ describe User do user = create(:user) group.add_developer(user) + expect(user.authorized_projects).to include(project) group.destroy + expect(user.authorized_projects).not_to include(project) end end @@ -1729,7 +1870,7 @@ describe User do end end - describe '#refresh_authorized_projects', clean_gitlab_redis_shared_state: true do + describe '#refresh_authorized_projects', :clean_gitlab_redis_shared_state do let(:project1) { create(:project) } let(:project2) { create(:project) } let(:user) { create(:user) } @@ -2018,7 +2159,9 @@ describe User do it 'creates the namespace' do expect(user.namespace).to be_nil + user.save! + expect(user.namespace).not_to be_nil end end @@ -2039,11 +2182,13 @@ describe User do it 'updates the namespace name' do user.update_attributes!(username: new_username) + expect(user.namespace.name).to eq(new_username) end it 'updates the namespace path' do user.update_attributes!(username: new_username) + expect(user.namespace.path).to eq(new_username) end @@ -2057,6 +2202,7 @@ describe User do it 'adds the namespace errors to the user' do user.update_attributes(username: new_username) + expect(user.errors.full_messages.first).to eq('Namespace name has already been taken') end end @@ -2073,17 +2219,39 @@ describe User do end end - describe '#verified_email?' do - it 'returns true when the email is the primary email' do - user = build :user, email: 'email@example.com' + describe '#username_changed_hook' do + context 'for a new user' do + let(:user) { build(:user) } + + it 'does not trigger system hook' do + expect(user).not_to receive(:system_hook_service) - expect(user.verified_email?('email@example.com')).to be true + user.save! + end end - it 'returns false when the email is not the primary email' do - user = build :user, email: 'email@example.com' + context 'for an existing user' do + let(:user) { create(:user, username: 'old-username') } + + context 'when the username is changed' do + let(:new_username) { 'very-new-name' } - expect(user.verified_email?('other_email@example.com')).to be false + it 'triggers the rename system hook' do + system_hook_service = SystemHooksService.new + expect(system_hook_service).to receive(:execute_hooks_for).with(user, :rename) + expect(user).to receive(:system_hook_service).and_return(system_hook_service) + + user.update_attributes!(username: new_username) + end + end + + context 'when the username is not changed' do + it 'does not trigger system hook' do + expect(user).not_to receive(:system_hook_service) + + user.update_attributes!(email: 'asdf@asdf.com') + end + end end end @@ -2093,36 +2261,43 @@ describe User do context 'oauth user' do it 'returns true if name can be synced' do stub_omniauth_setting(sync_profile_attributes: %w(name location)) + expect(user.sync_attribute?(:name)).to be_truthy end it 'returns true if email can be synced' do stub_omniauth_setting(sync_profile_attributes: %w(name email)) + expect(user.sync_attribute?(:email)).to be_truthy end it 'returns true if location can be synced' do stub_omniauth_setting(sync_profile_attributes: %w(location email)) + expect(user.sync_attribute?(:email)).to be_truthy end it 'returns false if name can not be synced' do stub_omniauth_setting(sync_profile_attributes: %w(location email)) + expect(user.sync_attribute?(:name)).to be_falsey end it 'returns false if email can not be synced' do stub_omniauth_setting(sync_profile_attributes: %w(location email)) + expect(user.sync_attribute?(:name)).to be_falsey end it 'returns false if location can not be synced' do stub_omniauth_setting(sync_profile_attributes: %w(location email)) + expect(user.sync_attribute?(:name)).to be_falsey end it 'returns true for all syncable attributes if all syncable attributes can be synced' do stub_omniauth_setting(sync_profile_attributes: true) + expect(user.sync_attribute?(:name)).to be_truthy expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:location)).to be_truthy @@ -2138,6 +2313,7 @@ describe User do context 'ldap user' do it 'returns true for email if ldap user' do allow(user).to receive(:ldap_user?).and_return(true) + expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:location)).to be_falsey @@ -2146,10 +2322,56 @@ describe User do it 'returns true for email and location if ldap user and location declared as syncable' do allow(user).to receive(:ldap_user?).and_return(true) stub_omniauth_setting(sync_profile_attributes: %w(location)) + expect(user.sync_attribute?(:name)).to be_falsey expect(user.sync_attribute?(:email)).to be_truthy expect(user.sync_attribute?(:location)).to be_truthy end end end + + describe '#confirm_deletion_with_password?' do + where( + password_automatically_set: [true, false], + ldap_user: [true, false], + password_authentication_disabled: [true, false] + ) + + with_them do + let!(:user) { create(:user, password_automatically_set: password_automatically_set) } + let!(:identity) { create(:identity, user: user) if ldap_user } + + # Only confirm deletion with password if all inputs are false + let(:expected) { !(password_automatically_set || ldap_user || password_authentication_disabled) } + + before do + stub_application_setting(password_authentication_enabled: !password_authentication_disabled) + end + + it 'returns false unless all inputs are true' do + expect(user.confirm_deletion_with_password?).to eq(expected) + end + end + end + + describe '#delete_async' do + let(:user) { create(:user) } + let(:deleted_by) { create(:user) } + + it 'blocks the user then schedules them for deletion if a hard delete is specified' do + expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, hard_delete: true) + + user.delete_async(deleted_by: deleted_by, params: { hard_delete: true }) + + expect(user).to be_blocked + end + + it 'schedules user for deletion without blocking them' do + expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, {}) + + user.delete_async(deleted_by: deleted_by) + + expect(user).not_to be_blocked + end + end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 9ef8d117123..a7227b38850 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -80,7 +80,7 @@ describe WikiPage do context "when initialized with an existing gollum page" do before do create_page("test page", "test content") - @page = wiki.wiki.paged("test page") + @page = wiki.wiki.page(title: "test page") @wiki_page = described_class.new(wiki, @page, true) end @@ -105,7 +105,7 @@ describe WikiPage do end it "sets the version attribute" do - expect(@wiki_page.version).to be_a Gollum::Git::Commit + expect(@wiki_page.version).to be_a Gitlab::Git::WikiPageVersion end end end @@ -321,14 +321,14 @@ describe WikiPage do end it 'returns true when requesting an old version' do - old_version = @page.versions.last.to_s + old_version = @page.versions.last.id old_page = wiki.find_page('Update', old_version) expect(old_page.historical?).to eq true end it 'returns false when requesting latest version' do - latest_version = @page.versions.first.to_s + latest_version = @page.versions.first.id latest_page = wiki.find_page('Update', latest_version) expect(latest_page.historical?).to eq false @@ -393,7 +393,7 @@ describe WikiPage do end def commit_details - { name: user.name, email: user.email, message: "test commit" } + Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit") end def create_page(name, content) @@ -401,8 +401,8 @@ describe WikiPage do end def destroy_page(title) - page = wiki.wiki.paged(title) - wiki.wiki.delete_page(page, commit_details) + page = wiki.wiki.page(title: title) + wiki.delete_page(page, "test commit") end def get_slugs(page_or_dir) diff --git a/spec/policies/gcp/cluster_policy_spec.rb b/spec/policies/gcp/cluster_policy_spec.rb new file mode 100644 index 00000000000..e213aa3d557 --- /dev/null +++ b/spec/policies/gcp/cluster_policy_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gcp::ClusterPolicy, :models do + set(:project) { create(:project) } + set(:cluster) { create(:gcp_cluster, project: project) } + let(:user) { create(:user) } + let(:policy) { described_class.new(user, cluster) } + + describe 'rules' do + context 'when developer' do + before do + project.add_developer(user) + end + + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + + context 'when master' do + before do + project.add_master(user) + end + + it { expect(policy).to be_allowed :update_cluster } + it { expect(policy).to be_allowed :admin_cluster } + end + end +end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index a6bf70c1e09..5b8cf2e6ab5 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -51,4 +51,41 @@ describe GlobalPolicy do end end end + + describe "create fork" do + context "when user has not exceeded project limit" do + it { is_expected.to be_allowed(:create_fork) } + end + + context "when user has exceeded project limit" do + let(:current_user) { create(:user, projects_limit: 0) } + + it { is_expected.not_to be_allowed(:create_fork) } + end + + context "when user is a master in a group" do + let(:group) { create(:group) } + let(:current_user) { create(:user, projects_limit: 0) } + + before do + group.add_master(current_user) + end + + it { is_expected.to be_allowed(:create_fork) } + end + end + + describe 'custom attributes' do + context 'regular user' do + it { is_expected.not_to be_allowed(:read_custom_attribute) } + it { is_expected.not_to be_allowed(:update_custom_attribute) } + end + + context 'admin' do + let(:current_user) { create(:user, :admin) } + + it { is_expected.to be_allowed(:read_custom_attribute) } + it { is_expected.to be_allowed(:update_custom_attribute) } + end + end end diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb new file mode 100644 index 00000000000..2cf669e8191 --- /dev/null +++ b/spec/policies/issuable_policy_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe IssuablePolicy, models: true do + describe '#rules' do + context 'when discussion is locked for the issuable' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project, discussion_locked: true) } + let(:policies) { described_class.new(user, issue) } + + context 'when the user is not a project member' do + it 'can not create a note' do + expect(policies).to be_disallowed(:create_note) + end + end + + context 'when the user is a project member' do + before do + project.add_guest(user) + end + + it 'can create a note' do + expect(policies).to be_allowed(:create_note) + end + end + end + end +end diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb new file mode 100644 index 00000000000..e52ff02e5f0 --- /dev/null +++ b/spec/policies/namespace_policy_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe NamespacePolicy do + let(:current_user) { create(:user) } + let(:namespace) { current_user.namespace } + + subject { described_class.new(current_user, namespace) } + + context "create projects" do + context "user namespace" do + it { is_expected.to be_allowed(:create_projects) } + end + + context "user who has exceeded project limit" do + let(:current_user) { create(:user, projects_limit: 0) } + + it { is_expected.not_to be_allowed(:create_projects) } + end + end +end diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb new file mode 100644 index 00000000000..58d36a2c84e --- /dev/null +++ b/spec/policies/note_policy_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe NotePolicy, mdoels: true do + describe '#rules' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + + def policies(noteable = nil) + return @policies if @policies + + noteable ||= issue + note = create(:note, noteable: noteable, author: user, project: project) + + @policies = described_class.new(user, note) + end + + context 'when the project is public' do + context 'when the note author is not a project member' do + it 'can edit a note' do + expect(policies).to be_allowed(:update_note) + expect(policies).to be_allowed(:admin_note) + expect(policies).to be_allowed(:resolve_note) + expect(policies).to be_allowed(:read_note) + end + end + + context 'when the noteable is a snippet' do + it 'can edit note' do + policies = policies(create(:project_snippet, project: project)) + + expect(policies).to be_allowed(:update_note) + expect(policies).to be_allowed(:admin_note) + expect(policies).to be_allowed(:resolve_note) + expect(policies).to be_allowed(:read_note) + end + end + + context 'when a discussion is locked' do + before do + issue.update_attribute(:discussion_locked, true) + end + + context 'when the note author is a project member' do + before do + project.add_developer(user) + end + + it 'can edit a note' do + expect(policies).to be_allowed(:update_note) + expect(policies).to be_allowed(:admin_note) + expect(policies).to be_allowed(:resolve_note) + expect(policies).to be_allowed(:read_note) + end + end + + context 'when the note author is not a project member' do + it 'can not edit a note' do + expect(policies).to be_disallowed(:update_note) + expect(policies).to be_disallowed(:admin_note) + expect(policies).to be_disallowed(:resolve_note) + end + + it 'can read a note' do + expect(policies).to be_allowed(:read_note) + end + end + end + end + end +end diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb index e4886a8f019..f7ceaf844be 100644 --- a/spec/presenters/ci/pipeline_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -51,4 +51,21 @@ describe Ci::PipelinePresenter do end end end + + context '#failure_reason' do + context 'when pipeline has failure reason' do + it 'represents a failure reason sentence' do + pipeline.failure_reason = :config_error + + expect(presenter.failure_reason) + .to eq 'CI/CD YAML configuration error!' + end + end + + context 'when pipeline does not have failure reason' do + it 'returns nil' do + expect(presenter.failure_reason).to be_nil + end + end + end end diff --git a/spec/presenters/gcp/cluster_presenter_spec.rb b/spec/presenters/gcp/cluster_presenter_spec.rb new file mode 100644 index 00000000000..8d86dc31582 --- /dev/null +++ b/spec/presenters/gcp/cluster_presenter_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gcp::ClusterPresenter do + let(:project) { create(:project) } + let(:cluster) { create(:gcp_cluster, project: project) } + + subject(:presenter) do + described_class.new(cluster) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end + + describe '#initialize' do + it 'takes a cluster and optional params' do + expect { presenter }.not_to raise_error + end + + it 'exposes cluster' do + expect(presenter.cluster).to eq(cluster) + end + + it 'forwards missing methods to cluster' do + expect(presenter.gcp_cluster_zone).to eq(cluster.gcp_cluster_zone) + end + end + + describe '#gke_cluster_url' do + subject { described_class.new(cluster).gke_cluster_url } + + it { is_expected.to include(cluster.gcp_cluster_zone) } + it { is_expected.to include(cluster.gcp_cluster_name) } + end +end diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 2187be0190d..5e114434a67 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -300,6 +300,10 @@ describe MergeRequestPresenter do described_class.new(resource, current_user: user).remove_wip_path end + before do + allow(resource).to receive(:work_in_progress?).and_return(true) + end + context 'when merge request enabled and has permission' do it 'has remove_wip_path' do allow(project).to receive(:merge_requests_enabled?) { true } diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 50d0f72f6bc..35ca3635a9d 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -35,7 +35,7 @@ describe API::AccessRequests do user = public_send(type) get api("/#{source_type.pluralize}/#{source.id}/access_requests", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -45,7 +45,7 @@ describe API::AccessRequests do it 'returns access requesters' do get api("/#{source_type.pluralize}/#{source.id}/access_requests", master) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -68,7 +68,7 @@ describe API::AccessRequests do user = public_send(type) post api("/#{source_type.pluralize}/#{source.id}/access_requests", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end.not_to change { source.requesters.count } end end @@ -80,7 +80,7 @@ describe API::AccessRequests do expect do post api("/#{source_type.pluralize}/#{source.id}/access_requests", access_requester) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end.not_to change { source.requesters.count } end end @@ -95,7 +95,7 @@ describe API::AccessRequests do expect do post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end.not_to change { source.requesters.count } end end @@ -104,7 +104,7 @@ describe API::AccessRequests do expect do post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.requesters.count }.by(1) # User attributes @@ -135,7 +135,7 @@ describe API::AccessRequests do user = public_send(type) put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -147,7 +147,7 @@ describe API::AccessRequests do put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", master), access_level: Member::MASTER - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) # User attributes expect(json_response['id']).to eq(access_requester.id) @@ -166,7 +166,7 @@ describe API::AccessRequests do expect do put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{stranger.id}/approve", master) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end.not_to change { source.members.count } end end @@ -187,7 +187,7 @@ describe API::AccessRequests do user = public_send(type) delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -198,7 +198,7 @@ describe API::AccessRequests do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", access_requester) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { source.requesters.count }.by(-1) end end @@ -208,7 +208,7 @@ describe API::AccessRequests do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", master) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { source.requesters.count }.by(-1) end @@ -217,7 +217,7 @@ describe API::AccessRequests do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{developer.id}", master) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end.not_to change { source.requesters.count } end end @@ -227,7 +227,7 @@ describe API::AccessRequests do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{stranger.id}", master) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end.not_to change { source.requesters.count } end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 7a0765c1fae..eaf12f71421 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -18,7 +18,7 @@ describe API::AwardEmoji do it "returns an array of award_emoji" do get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award_emoji.name) end @@ -26,7 +26,7 @@ describe API::AwardEmoji do it "returns a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/award_emoji", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -34,7 +34,7 @@ describe API::AwardEmoji do it "returns an array of award_emoji" do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['name']).to eq(downvote.name) @@ -48,7 +48,7 @@ describe API::AwardEmoji do it 'returns the awarded emoji' do get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award.name) end @@ -60,7 +60,7 @@ describe API::AwardEmoji do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -71,7 +71,7 @@ describe API::AwardEmoji do it 'returns an array of award emoji' do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(rocket.name) end @@ -82,7 +82,7 @@ describe API::AwardEmoji do it "returns the award emoji" do get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(award_emoji.name) expect(json_response['awardable_id']).to eq(issue.id) expect(json_response['awardable_type']).to eq("Issue") @@ -91,7 +91,7 @@ describe API::AwardEmoji do it "returns a 404 error if the award is not found" do get api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -99,7 +99,7 @@ describe API::AwardEmoji do it 'returns the award emoji' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(downvote.name) expect(json_response['awardable_id']).to eq(merge_request.id) expect(json_response['awardable_type']).to eq("MergeRequest") @@ -113,7 +113,7 @@ describe API::AwardEmoji do it 'returns the awarded emoji' do get api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(award.name) expect(json_response['awardable_id']).to eq(snippet.id) expect(json_response['awardable_type']).to eq("Snippet") @@ -126,7 +126,7 @@ describe API::AwardEmoji do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -137,7 +137,7 @@ describe API::AwardEmoji do it 'returns an award emoji' do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).not_to be_an Array expect(json_response['name']).to eq(rocket.name) end @@ -150,7 +150,7 @@ describe API::AwardEmoji do it "creates a new award emoji" do post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'blowfish' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -158,19 +158,19 @@ describe API::AwardEmoji do it "returns a 400 bad request error if the name is not given" do post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if the user is not authenticated" do post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), name: 'thumbsup' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns a 404 error if the user authored issue" do post api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "normalizes +1 as thumbsup award" do @@ -184,7 +184,7 @@ describe API::AwardEmoji do post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") end end @@ -196,7 +196,7 @@ describe API::AwardEmoji do post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -211,14 +211,14 @@ describe API::AwardEmoji do post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' end.to change { note.award_emoji.count }.from(0).to(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['user']['username']).to eq(user.username) end it "it returns 404 error when user authored note" do post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "normalizes +1 as thumbsup award" do @@ -232,7 +232,7 @@ describe API::AwardEmoji do post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") end end @@ -244,14 +244,14 @@ describe API::AwardEmoji do expect do delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/#{award_emoji.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { issue.award_emoji.count }.from(1).to(0) end it 'returns a 404 error when the award emoji can not be found' do delete api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -264,14 +264,14 @@ describe API::AwardEmoji do expect do delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/award_emoji/#{downvote.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { merge_request.award_emoji.count }.from(1).to(0) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -287,7 +287,7 @@ describe API::AwardEmoji do expect do delete api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { snippet.award_emoji.count }.from(1).to(0) end @@ -304,7 +304,7 @@ describe API::AwardEmoji do expect do delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { note.award_emoji.count }.from(1).to(0) end diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index fcfa4ddfbfe..546a1697e56 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -44,7 +44,7 @@ describe API::Boards do it "returns authentication error" do get api(base_url) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -52,7 +52,7 @@ describe API::Boards do it "returns the project issue board" do get api(base_url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -70,7 +70,7 @@ describe API::Boards do it 'returns issue board lists' do get api(base_url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(2) @@ -80,7 +80,7 @@ describe API::Boards do it 'returns 404 if board not found' do get api("/projects/#{project.id}/boards/22343/lists", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -90,7 +90,7 @@ describe API::Boards do it 'returns a list' do get api("#{base_url}/#{dev_list.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(dev_list.id) expect(json_response['label']['name']).to eq(dev_label.title) expect(json_response['position']).to eq(1) @@ -99,7 +99,7 @@ describe API::Boards do it 'returns 404 if list not found' do get api("#{base_url}/5324", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -113,7 +113,7 @@ describe API::Boards do post api(base_url, user), label_id: group_label.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(group_label.title) expect(json_response['position']).to eq(3) end @@ -121,7 +121,7 @@ describe API::Boards do it 'creates a new issue board list for project labels' do post api(base_url, user), label_id: ux_label.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(ux_label.title) expect(json_response['position']).to eq(3) end @@ -129,13 +129,13 @@ describe API::Boards do it 'returns 400 when creating a new list if label_id is invalid' do post api(base_url, user), label_id: 23423 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 403 for project members with guest role' do put api("#{base_url}/#{test_list.id}", guest), position: 1 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -146,7 +146,7 @@ describe API::Boards do put api("#{base_url}/#{test_list.id}", user), position: 1 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['position']).to eq(1) end @@ -154,14 +154,14 @@ describe API::Boards do put api("#{base_url}/44444", user), position: 1 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 403 for project members with guest role" do put api("#{base_url}/#{test_list.id}", guest), position: 1 - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -171,19 +171,19 @@ describe API::Boards do it "rejects a non member from deleting a list" do delete api("#{base_url}/#{dev_list.id}", non_member) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "rejects a user with guest role from deleting a list" do delete api("#{base_url}/#{dev_list.id}", guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns 404 error if list id not found" do delete api("#{base_url}/44444", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "when the user is project owner" do @@ -196,7 +196,7 @@ describe API::Boards do it "deletes the list if an admin requests it" do delete api("#{base_url}/#{dev_list.id}", owner) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it_behaves_like '412 response' do diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 16b12446ed4..e433597f58b 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -110,6 +110,15 @@ describe API::Branches do end end + context 'when the branch refname is invalid' do + let(:branch_name) { 'branch*' } + let(:message) { 'The branch refname is invalid' } + + it_behaves_like '400 response' do + let(:request) { get api(route, current_user) } + end + end + context 'when repository is disabled' do include_context 'disabled repository' @@ -234,6 +243,15 @@ describe API::Branches do end end + context 'when the branch refname is invalid' do + let(:branch_name) { 'branch*' } + let(:message) { 'The branch refname is invalid' } + + it_behaves_like '400 response' do + let(:request) { put api(route, current_user) } + end + end + context 'when repository is disabled' do include_context 'disabled repository' @@ -359,6 +377,15 @@ describe API::Branches do end end + context 'when the branch refname is invalid' do + let(:branch_name) { 'branch*' } + let(:message) { 'The branch refname is invalid' } + + it_behaves_like '400 response' do + let(:request) { put api(route, current_user) } + end + end + context 'when repository is disabled' do include_context 'disabled repository' @@ -520,6 +547,15 @@ describe API::Branches do expect(response).to have_gitlab_http_status(404) end + context 'when the branch refname is invalid' do + let(:branch_name) { 'branch*' } + let(:message) { 'The branch refname is invalid' } + + it_behaves_like '400 response' do + let(:request) { delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) } + end + end + it_behaves_like '412 response' do let(:request) { api("/projects/#{project.id}/repository/branches/#{branch_name}", user) } end diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb index eacc575d97f..fe8a14fae9e 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -9,13 +9,13 @@ describe API::BroadcastMessages do it 'returns a 401 for anonymous users' do get api('/broadcast_messages') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do get api('/broadcast_messages', user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns an Array of BroadcastMessages for admins' do @@ -23,7 +23,7 @@ describe API::BroadcastMessages do get api('/broadcast_messages', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_kind_of(Array) expect(json_response.first.keys) @@ -35,19 +35,19 @@ describe API::BroadcastMessages do it 'returns a 401 for anonymous users' do get api("/broadcast_messages/#{message.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do get api("/broadcast_messages/#{message.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns the specified message for admins' do get api("/broadcast_messages/#{message.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq message.id expect(json_response.keys) .to match_array(%w(id message starts_at ends_at color font active)) @@ -58,13 +58,13 @@ describe API::BroadcastMessages do it 'returns a 401 for anonymous users' do post api('/broadcast_messages'), attributes_for(:broadcast_message) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do post api('/broadcast_messages', user), attributes_for(:broadcast_message) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'as an admin' do @@ -74,7 +74,7 @@ describe API::BroadcastMessages do post api('/broadcast_messages', admin), attrs - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'message is missing' end @@ -83,7 +83,7 @@ describe API::BroadcastMessages do travel_to(time) do post api('/broadcast_messages', admin), message: 'Test message' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' expect(json_response['ends_at']).to eq '2016-07-02T11:11:12.000Z' end @@ -94,7 +94,7 @@ describe API::BroadcastMessages do post api('/broadcast_messages', admin), attrs - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['color']).to eq attrs[:color] expect(json_response['font']).to eq attrs[:font] end @@ -106,14 +106,14 @@ describe API::BroadcastMessages do put api("/broadcast_messages/#{message.id}"), attributes_for(:broadcast_message) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do put api("/broadcast_messages/#{message.id}", user), attributes_for(:broadcast_message) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'as an admin' do @@ -122,7 +122,7 @@ describe API::BroadcastMessages do put api("/broadcast_messages/#{message.id}", admin), attrs - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['color']).to eq attrs[:color] expect(json_response['font']).to eq attrs[:font] end @@ -134,7 +134,7 @@ describe API::BroadcastMessages do put api("/broadcast_messages/#{message.id}", admin), attrs - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' expect(json_response['ends_at']).to eq '2016-07-02T13:11:12.000Z' end @@ -145,7 +145,7 @@ describe API::BroadcastMessages do put api("/broadcast_messages/#{message.id}", admin), attrs - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect { message.reload }.to change { message.message }.to('new message') end end @@ -156,14 +156,14 @@ describe API::BroadcastMessages do delete api("/broadcast_messages/#{message.id}"), attributes_for(:broadcast_message) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do delete api("/broadcast_messages/#{message.id}", user), attributes_for(:broadcast_message) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it_behaves_like '412 response' do @@ -174,7 +174,7 @@ describe API::BroadcastMessages do expect do delete api("/broadcast_messages/#{message.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { BroadcastMessage.count }.by(-1) end end diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb index 76521e55994..3b858c40fd6 100644 --- a/spec/requests/api/circuit_breakers_spec.rb +++ b/spec/requests/api/circuit_breakers_spec.rb @@ -8,13 +8,13 @@ describe API::CircuitBreakers do it 'returns a 401 for anonymous users' do get api('/circuit_breakers/repository_storage') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do get api('/circuit_breakers/repository_storage', user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns an Array of storages' do @@ -24,7 +24,7 @@ describe API::CircuitBreakers do get api('/circuit_breakers/repository_storage', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_kind_of(Array) expect(json_response.first['storage_name']).to eq('broken') expect(json_response.first['failing_on_hosts']).to eq(['web01']) @@ -39,7 +39,7 @@ describe API::CircuitBreakers do get api('/circuit_breakers/repository_storage/failing', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_kind_of(Array) end end @@ -51,7 +51,7 @@ describe API::CircuitBreakers do delete api('/circuit_breakers/repository_storage', admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index e4c73583545..ffa17d296e8 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -39,7 +39,7 @@ describe API::CommitStatuses do end it 'returns latest commit statuses' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -55,7 +55,7 @@ describe API::CommitStatuses do end it 'returns all commit statuses' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status1.id, status2.id, @@ -70,7 +70,7 @@ describe API::CommitStatuses do end it 'returns latest commit statuses for specific ref' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status3.id, status5.id) @@ -83,7 +83,7 @@ describe API::CommitStatuses do end it 'return latest commit statuses for specific name' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(statuses_id).to contain_exactly(status4.id, status5.id) @@ -110,7 +110,7 @@ describe API::CommitStatuses do end it "does not return project commits" do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -120,7 +120,7 @@ describe API::CommitStatuses do end it "does not return project commits" do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -135,7 +135,7 @@ describe API::CommitStatuses do it 'creates commit status' do post api(post_url, developer), state: status - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq(status) expect(json_response['name']).to eq('default') @@ -159,7 +159,7 @@ describe API::CommitStatuses do it "to #{status}" do expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count } - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['status']).to eq(status) end end @@ -181,7 +181,7 @@ describe API::CommitStatuses do it 'creates commit status' do subject - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') @@ -197,7 +197,7 @@ describe API::CommitStatuses do it 'sets head pipeline' do subject - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(merge_request.reload.head_pipeline).not_to be_nil end end @@ -224,7 +224,7 @@ describe API::CommitStatuses do end it 'updates a commit status' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') expect(json_response['name']).to eq('coverage') @@ -250,7 +250,7 @@ describe API::CommitStatuses do end it 'correctly posts a new commit status' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['sha']).to eq(commit.id) expect(json_response['status']).to eq('success') end @@ -268,7 +268,7 @@ describe API::CommitStatuses do end it 'does not create commit status' do - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -278,7 +278,7 @@ describe API::CommitStatuses do end it 'does not create commit status' do - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -290,7 +290,7 @@ describe API::CommitStatuses do end it 'returns not found error' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -301,7 +301,7 @@ describe API::CommitStatuses do end it 'responds with bad request status and validation errors' do - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['target_url']) .to include 'must be a valid URL' end @@ -314,7 +314,7 @@ describe API::CommitStatuses do end it 'does not create commit status' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -324,7 +324,7 @@ describe API::CommitStatuses do end it 'does not create commit status' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -334,7 +334,7 @@ describe API::CommitStatuses do end it 'does not create commit status' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index f663719d28c..0d2bd3207c0 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -24,7 +24,7 @@ describe API::Commits do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/commits') expect(json_response.first['id']).to eq(commit.id) expect(json_response.first['committer_name']).to eq(commit.committer_name) @@ -119,7 +119,7 @@ describe API::Commits do it "returns an invalid parameter error message" do get api("/projects/#{project_id}/repository/commits?since=invalid-date", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('since is invalid') end end @@ -198,13 +198,13 @@ describe API::Commits do it 'returns a 403 unauthorized for user without permissions' do post api(url, guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns a 400 bad request if no params are given' do post api(url, user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end describe 'create' do @@ -248,7 +248,7 @@ describe API::Commits do it 'returns a 400 bad request if file exists' do post api(url, user), invalid_c_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'with project path containing a dot in URL' do @@ -257,7 +257,7 @@ describe API::Commits do it 'a new file in project repo' do post api(url, user), valid_c_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end end @@ -292,14 +292,14 @@ describe API::Commits do it 'an existing file in project repo' do post api(url, user), valid_d_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post api(url, user), invalid_d_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -337,14 +337,14 @@ describe API::Commits do it 'an existing file in project repo' do post api(url, user), valid_m_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post api(url, user), invalid_m_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -380,14 +380,14 @@ describe API::Commits do it 'an existing file in project repo' do post api(url, user), valid_u_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post api(url, user), invalid_u_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -453,14 +453,14 @@ describe API::Commits do it 'are commited as one in project repo' do post api(url, user), valid_mo_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'return a 400 bad request if there are any issues' do post api(url, user), invalid_mo_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -491,6 +491,7 @@ describe API::Commits do expect(json_response['stats']['deletions']).to eq(commit.stats.deletions) expect(json_response['stats']['total']).to eq(commit.stats.total) expect(json_response['status']).to be_nil + expect(json_response['last_pipeline']).to be_nil end context 'when ref does not exist' do @@ -570,9 +571,13 @@ describe API::Commits do it 'includes a "created" status' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/commit/detail') expect(json_response['status']).to eq('created') + expect(json_response['last_pipeline']['id']).to eq(pipeline.id) + expect(json_response['last_pipeline']['ref']).to eq(pipeline.ref) + expect(json_response['last_pipeline']['sha']).to eq(pipeline.sha) + expect(json_response['last_pipeline']['status']).to eq(pipeline.status) end context 'when pipeline succeeds' do @@ -583,7 +588,7 @@ describe API::Commits do it 'includes a "success" status' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/commit/detail') expect(json_response['status']).to eq('success') end diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 684877c33c0..1f1e6ea17e4 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -48,7 +48,7 @@ describe API::DeployKeys do it 'returns array of ssh keys' do get api("/projects/#{project.id}/deploy_keys", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['title']).to eq(deploy_key.title) @@ -59,14 +59,14 @@ describe API::DeployKeys do it 'returns a single key' do get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(deploy_key.title) end it 'returns 404 Not Found with invalid ID' do get api("/projects/#{project.id}/deploy_keys/404", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -74,14 +74,14 @@ describe API::DeployKeys do it 'does not create an invalid ssh key' do post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create a key without title' do post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') end @@ -98,7 +98,7 @@ describe API::DeployKeys do post api("/projects/#{project.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title } end.not_to change { project.deploy_keys.count } - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'joins an existing ssh key to a new project' do @@ -106,7 +106,7 @@ describe API::DeployKeys do post api("/projects/#{project2.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title } end.to change { project2.deploy_keys.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'accepts can_push parameter' do @@ -114,7 +114,7 @@ describe API::DeployKeys do post api("/projects/#{project.id}/deploy_keys", admin), key_attrs - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['can_push']).to eq(true) end end @@ -130,7 +130,7 @@ describe API::DeployKeys do put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), { title: 'new title' } end.not_to change(deploy_key, :title) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not update a public deploy key as non admin' do @@ -138,7 +138,7 @@ describe API::DeployKeys do put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), { title: 'new title' } end.not_to change(deploy_key, :title) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not update a private key with invalid title' do @@ -148,7 +148,7 @@ describe API::DeployKeys do put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: '' } end.not_to change(deploy_key, :title) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'updates a private ssh key with correct attributes' do @@ -181,14 +181,14 @@ describe API::DeployKeys do expect do delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { project.deploy_keys.count }.by(-1) end it 'returns 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/deploy_keys/404", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -205,7 +205,7 @@ describe API::DeployKeys do post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", admin) end.to change { project2.deploy_keys.count }.from(0).to(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(deploy_key.id) end end @@ -214,7 +214,7 @@ describe API::DeployKeys do it 'returns a 404 error' do post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 90d78d060ca..c7977e624ff 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -15,7 +15,7 @@ describe API::Deployments do it 'returns projects deployments' do get api("/projects/#{project.id}/deployments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -28,7 +28,7 @@ describe API::Deployments do it 'returns a 404 status code' do get api("/projects/#{project.id}/deployments", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -38,7 +38,7 @@ describe API::Deployments do it 'returns the projects deployment' do get api("/projects/#{project.id}/deployments/#{deployment.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['sha']).to match /\A\h{40}\z/ expect(json_response['id']).to eq(deployment.id) end @@ -48,7 +48,7 @@ describe API::Deployments do it 'returns a 404 status code' do get api("/projects/#{project.id}/deployments/#{deployment.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 868fef65c1c..308134eba72 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -8,7 +8,7 @@ describe 'doorkeeper access' do describe "unauthenticated" do it "returns authentication success" do get api("/user"), access_token: token.token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end include_examples 'user login request with unique ip limit' do @@ -21,14 +21,14 @@ describe 'doorkeeper access' do describe "when token invalid" do it "returns authentication error" do get api("/user"), access_token: "123a" - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end - describe "authorization by private token" do + describe "authorization by OAuth token" do it "returns authentication success" do get api("/user", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end include_examples 'user login request with unique ip limit' do @@ -39,20 +39,20 @@ describe 'doorkeeper access' do end describe "when user is blocked" do - it "returns authentication error" do + it "returns authorization error" do user.block get api("/user"), access_token: token.token - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end describe "when user is ldap_blocked" do - it "returns authentication error" do + it "returns authorization error" do user.ldap_block get api("/user"), access_token: token.token - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 2361809e0e1..3665cfd7241 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -20,11 +20,12 @@ describe API::Environments do path path_with_namespace star_count forks_count created_at last_activity_at + avatar_url ) get api("/projects/#{project.id}/environments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -38,7 +39,7 @@ describe API::Environments do it 'returns a 404 status code' do get api("/projects/#{project.id}/environments", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -48,7 +49,7 @@ describe API::Environments do it 'creates a environment with valid params' do post api("/projects/#{project.id}/environments", user), name: "mepmep" - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('mepmep') expect(json_response['slug']).to eq('mepmep') expect(json_response['external']).to be nil @@ -57,19 +58,19 @@ describe API::Environments do it 'requires name to be passed' do post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if environment already exists' do post api("/projects/#{project.id}/environments", user), name: environment.name - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if slug is specified' do post api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") end end @@ -78,7 +79,7 @@ describe API::Environments do it 'rejects the request' do post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 400 when the required params are missing' do @@ -93,7 +94,7 @@ describe API::Environments do put api("/projects/#{project.id}/environments/#{environment.id}", user), name: 'Mepmep', external_url: url - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') expect(json_response['external_url']).to eq(url) end @@ -103,7 +104,7 @@ describe API::Environments do api_url = api("/projects/#{project.id}/environments/#{environment.id}", user) put api_url, slug: slug + "-foo" - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") end @@ -112,7 +113,7 @@ describe API::Environments do put api("/projects/#{project.id}/environments/#{environment.id}", user), name: 'Mepmep' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') expect(json_response['external_url']).to eq(url) end @@ -120,7 +121,7 @@ describe API::Environments do it 'returns a 404 if the environment does not exist' do put api("/projects/#{project.id}/environments/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -129,13 +130,13 @@ describe API::Environments do it 'returns a 200 for an existing environment' do delete api("/projects/#{project.id}/environments/#{environment.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it 'returns a 404 for non existing id' do delete api("/projects/#{project.id}/environments/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -148,7 +149,7 @@ describe API::Environments do it 'rejects the request' do delete api("/projects/#{project.id}/environments/#{environment.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -163,7 +164,7 @@ describe API::Environments do end it 'returns a 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'actually stops the environment' do @@ -174,7 +175,7 @@ describe API::Environments do it 'returns a 404 for non existing id' do post api("/projects/#{project.id}/environments/12345/stop", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end end @@ -183,7 +184,7 @@ describe API::Environments do it 'rejects the request' do post api("/projects/#{project.id}/environments/#{environment.id}/stop", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index a23d28994ce..962c845f36d 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -14,7 +14,7 @@ describe API::Events do it 'returns authentication error' do get api('/events') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -22,7 +22,7 @@ describe API::Events do it 'returns users events' do get api('/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -35,7 +35,7 @@ describe API::Events do it 'returns no events' do get api("/users/#{user.id}/events", other_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty end end @@ -44,7 +44,7 @@ describe API::Events do it 'accepts a username' do get api("/users/#{user.username}/events", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -53,7 +53,7 @@ describe API::Events do it 'returns the events' do get api("/users/#{user.id}/events", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -72,7 +72,7 @@ describe API::Events do end it 'responds with HTTP 200 OK' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'includes the push payload as a Hash' do @@ -120,7 +120,7 @@ describe API::Events do it 'returns a 404 error if not found' do get api('/users/42/events', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end @@ -130,7 +130,7 @@ describe API::Events do it 'returns 404 for private project' do get api("/projects/#{private_project.id}/events") - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 200 status for a public project' do @@ -138,7 +138,7 @@ describe API::Events do get api("/projects/#{public_project.id}/events") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -146,7 +146,7 @@ describe API::Events do it 'returns 404' do get api("/projects/#{private_project.id}/events", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -154,7 +154,7 @@ describe API::Events do it 'returns project events' do get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -163,7 +163,7 @@ describe API::Events do it 'returns 404 if project does not exist' do get api("/projects/1234/events", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index 7e21006b254..267058d98ee 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -44,19 +44,19 @@ describe API::Features do it 'returns a 401 for anonymous users' do get api('/features') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do get api('/features', user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns the feature list for admins' do get api('/features', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to match_array(expected_features) end end @@ -68,20 +68,20 @@ describe API::Features do it 'returns a 401 for anonymous users' do post api("/features/#{feature_name}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do post api("/features/#{feature_name}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'when passed value=true' do it 'creates an enabled feature' do post api("/features/#{feature_name}", admin), value: 'true' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'on', @@ -91,7 +91,7 @@ describe API::Features do it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -104,7 +104,7 @@ describe API::Features do it 'creates an enabled feature for the given user when passed user=username' do post api("/features/#{feature_name}", admin), value: 'true', user: user.username - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -117,7 +117,7 @@ describe API::Features do it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -132,7 +132,7 @@ describe API::Features do it 'creates a feature with the given percentage if passed an integer' do post api("/features/#{feature_name}", admin), value: '50' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -154,7 +154,7 @@ describe API::Features do it 'enables the feature' do post api("/features/#{feature_name}", admin), value: 'true' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'on', @@ -164,7 +164,7 @@ describe API::Features do it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -177,7 +177,7 @@ describe API::Features do it 'enables the feature for the given user when passed user=username' do post api("/features/#{feature_name}", admin), value: 'true', user: user.username - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', @@ -195,7 +195,7 @@ describe API::Features do post api("/features/#{feature_name}", admin), value: 'false' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'off', @@ -208,7 +208,7 @@ describe API::Features do post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'off', @@ -221,7 +221,7 @@ describe API::Features do post api("/features/#{feature_name}", admin), value: 'false', user: user.username - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'off', @@ -237,7 +237,7 @@ describe API::Features do it 'updates the percentage of time if passed an integer' do post api("/features/#{feature_name}", admin), value: '30' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to eq( 'name' => 'my_feature', 'state' => 'conditional', diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 114019441a3..5d8338a3fb7 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -26,7 +26,7 @@ describe API::Files do it 'returns file attributes as json' do get api(route(file_path), current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) expect(json_response['file_name']).to eq('popen.rb') expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') @@ -38,7 +38,7 @@ describe API::Files do get api(route(file_path), current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq('application/json') end @@ -49,7 +49,7 @@ describe API::Files do get api(route(file_path), current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_name']).to eq('commit.js.coffee') expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n") end @@ -60,7 +60,7 @@ describe API::Files do get api(url, current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when mandatory params are not given' do @@ -122,7 +122,7 @@ describe API::Files do get api(url, current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns raw file info for files with dots' do @@ -131,7 +131,7 @@ describe API::Files do get api(url, current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns file by commit sha' do @@ -142,7 +142,7 @@ describe API::Files do get api(route(file_path) + "/raw", current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when mandatory params are not given' do @@ -209,7 +209,7 @@ describe API::Files do it "creates a new file in project repo" do post api(route(file_path), user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["file_path"]).to eq(CGI.unescape(file_path)) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -219,7 +219,7 @@ describe API::Files do it "returns a 400 bad request if no mandatory params given" do post api(route("any%2Etxt"), user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 if editor fails to create file" do @@ -228,7 +228,7 @@ describe API::Files do post api(route("any%2Etxt"), user), valid_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -237,7 +237,7 @@ describe API::Files do post api(route("new_file_with_author%2Etxt"), user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(response.content_type).to eq('application/json') last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(author_email) @@ -251,7 +251,7 @@ describe API::Files do it "creates a new file in project repo" do post api(route("newfile%2Erb"), user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -272,7 +272,7 @@ describe API::Files do it "updates existing file in project repo" do put api(route(file_path), user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -284,7 +284,7 @@ describe API::Files do put api(route(file_path), user), params_with_stale_id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.') end @@ -295,13 +295,13 @@ describe API::Files do put api(route(file_path), user), params_with_correct_id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns a 400 bad request if no params given" do put api(route(file_path), user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -310,7 +310,7 @@ describe API::Files do put api(route(file_path), user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(author_email) expect(last_commit.author_name).to eq(author_name) @@ -329,13 +329,13 @@ describe API::Files do it "deletes existing file in project repo" do delete api(route(file_path), user), valid_params - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it "returns a 400 bad request if no params given" do delete api(route(file_path), user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 if fails to delete file" do @@ -343,7 +343,7 @@ describe API::Files do delete api(route(file_path), user), valid_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -352,7 +352,7 @@ describe API::Files do delete api(route(file_path), user), valid_params - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end end end @@ -380,7 +380,7 @@ describe API::Files do it "remains unchanged" do get api(route(file_path), user), get_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(CGI.unescape(file_path)) expect(json_response['file_name']).to eq(CGI.unescape(file_path)) expect(json_response['content']).to eq(put_params[:content]) diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index 93b9cf85c1d..a4f198eb5c9 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -15,7 +15,7 @@ describe API::GroupVariables do it 'returns group variables' do get api("/groups/#{group.id}/variables", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a(Array) end end @@ -24,7 +24,7 @@ describe API::GroupVariables do it 'does not return group variables' do get api("/groups/#{group.id}/variables", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -32,7 +32,7 @@ describe API::GroupVariables do it 'does not return group variables' do get api("/groups/#{group.id}/variables") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -48,7 +48,7 @@ describe API::GroupVariables do it 'returns group variable details' do get api("/groups/#{group.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) end @@ -56,7 +56,7 @@ describe API::GroupVariables do it 'responds with 404 Not Found if requesting non-existing variable' do get api("/groups/#{group.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -64,7 +64,7 @@ describe API::GroupVariables do it 'does not return group variable details' do get api("/groups/#{group.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -72,7 +72,7 @@ describe API::GroupVariables do it 'does not return group variable details' do get api("/groups/#{group.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -90,7 +90,7 @@ describe API::GroupVariables do post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true end.to change {group.variables.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_truthy @@ -101,7 +101,7 @@ describe API::GroupVariables do post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' end.to change {group.variables.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey @@ -112,7 +112,7 @@ describe API::GroupVariables do post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2' end.to change {group.variables.count}.by(0) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -120,7 +120,7 @@ describe API::GroupVariables do it 'does not create variable' do post api("/groups/#{group.id}/variables", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -128,7 +128,7 @@ describe API::GroupVariables do it 'does not create variable' do post api("/groups/#{group.id}/variables") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -149,7 +149,7 @@ describe API::GroupVariables do updated_variable = group.variables.first - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') expect(updated_variable).to be_protected @@ -158,7 +158,7 @@ describe API::GroupVariables do it 'responds with 404 Not Found if requesting non-existing variable' do put api("/groups/#{group.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -166,7 +166,7 @@ describe API::GroupVariables do it 'does not update variable' do put api("/groups/#{group.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -174,7 +174,7 @@ describe API::GroupVariables do it 'does not update variable' do put api("/groups/#{group.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -191,14 +191,14 @@ describe API::GroupVariables do expect do delete api("/groups/#{group.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change {group.variables.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing variable' do delete api("/groups/#{group.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -210,7 +210,7 @@ describe API::GroupVariables do it 'does not delete variable' do delete api("/groups/#{group.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -218,7 +218,7 @@ describe API::GroupVariables do it 'does not delete variable' do delete api("/groups/#{group.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1671a046fdf..8ce9fcc80bf 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -23,7 +23,7 @@ describe API::Groups do it "returns public groups" do get api("/groups") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -36,7 +36,7 @@ describe API::Groups do it "normal user: returns an array of groups of user1" do get api("/groups", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -47,7 +47,7 @@ describe API::Groups do it "does not include statistics" do get api("/groups", user1), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first).not_to include 'statistics' @@ -58,7 +58,7 @@ describe API::Groups do it "admin: returns an array of all groups" do get api("/groups", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(2) @@ -67,7 +67,7 @@ describe API::Groups do it "does not include statistics by default" do get api("/groups", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first).not_to include('statistics') @@ -87,7 +87,7 @@ describe API::Groups do get api("/groups", admin), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response) @@ -99,7 +99,7 @@ describe API::Groups do it "returns all groups excluding skipped groups" do get api("/groups", admin), skip_groups: [group2.id] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -114,7 +114,7 @@ describe API::Groups do get api("/groups", user1), all_available: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(response_groups).to contain_exactly(public_group.name, group1.name) @@ -132,7 +132,7 @@ describe API::Groups do it "sorts by name ascending by default" do get api("/groups", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(response_groups).to eq([group3.name, group1.name]) @@ -141,7 +141,7 @@ describe API::Groups do it "sorts in descending order when passed" do get api("/groups", user1), sort: "desc" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(response_groups).to eq([group1.name, group3.name]) @@ -150,7 +150,7 @@ describe API::Groups do it "sorts by the order_by param" do get api("/groups", user1), order_by: "path" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(response_groups).to eq([group1.name, group3.name]) @@ -163,7 +163,7 @@ describe API::Groups do get api('/groups', user2), owned: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -176,12 +176,12 @@ describe API::Groups do context 'when unauthenticated' do it 'returns 404 for a private group' do get api("/groups/#{group2.id}") - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 200 for a public group' do get api("/groups/#{group1.id}") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -192,7 +192,7 @@ describe API::Groups do get api("/groups/#{group1.id}", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(group1.id) expect(json_response['name']).to eq(group1.name) expect(json_response['path']).to eq(group1.path) @@ -214,13 +214,13 @@ describe API::Groups do it "does not return a non existing group" do get api("/groups/1328", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not return a group not attached to user1" do get api("/groups/#{group2.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -228,14 +228,14 @@ describe API::Groups do it "returns any existing group" do get api("/groups/#{group2.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(group2.name) end it "does not return a non existing group" do get api("/groups/1328", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -243,20 +243,20 @@ describe API::Groups do it 'returns any existing group' do get api("/groups/#{group1.path}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(group1.name) end it 'does not return a non existing group' do get api('/groups/unknown', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not return a group not attached to user1' do get api("/groups/#{group2.path}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -268,7 +268,7 @@ describe API::Groups do it 'updates the group' do put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) expect(json_response['request_access_enabled']).to eq(true) end @@ -276,7 +276,7 @@ describe API::Groups do it 'returns 404 for a non existing group' do put api('/groups/1328', user1), name: new_group_name - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -284,7 +284,7 @@ describe API::Groups do it 'updates the group' do put api("/groups/#{group1.id}", admin), name: new_group_name - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) end end @@ -293,7 +293,7 @@ describe API::Groups do it 'does not updates the group' do put api("/groups/#{group1.id}", user2), name: new_group_name - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -301,7 +301,7 @@ describe API::Groups do it 'returns 404 when trying to update the group' do put api("/groups/#{group2.id}", user1), name: new_group_name - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -311,7 +311,7 @@ describe API::Groups do it "returns the group's projects" do get api("/groups/#{group1.id}/projects", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } @@ -322,7 +322,7 @@ describe API::Groups do it "returns the group's projects with simple representation" do get api("/groups/#{group1.id}/projects", user1), simple: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } @@ -335,7 +335,7 @@ describe API::Groups do get api("/groups/#{group1.id}/projects", user1), visibility: 'public' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an(Array) expect(json_response.length).to eq(1) @@ -345,13 +345,13 @@ describe API::Groups do it "does not return a non existing group" do get api("/groups/1328/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not return a group not attached to user1" do get api("/groups/#{group2.id}/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "only returns projects to which user has access" do @@ -359,7 +359,7 @@ describe API::Groups do get api("/groups/#{group1.id}/projects", user3) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project3.name) @@ -370,7 +370,7 @@ describe API::Groups do get api("/groups/#{project2.group.id}/projects", user3), owned: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) end @@ -380,7 +380,7 @@ describe API::Groups do get api("/groups/#{group1.id}/projects", user1), starred: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project1.name) end @@ -390,7 +390,7 @@ describe API::Groups do it "returns any existing group" do get api("/groups/#{group2.id}/projects", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) @@ -399,7 +399,7 @@ describe API::Groups do it "does not return a non existing group" do get api("/groups/1328/projects", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -407,7 +407,7 @@ describe API::Groups do it 'returns any existing group' do get api("/groups/#{group1.path}/projects", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) @@ -416,13 +416,13 @@ describe API::Groups do it 'does not return a non existing group' do get api('/groups/unknown/projects', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not return a group not attached to user1' do get api("/groups/#{group2.path}/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -432,7 +432,7 @@ describe API::Groups do it "does not create group" do post api("/groups", user1), attributes_for(:group) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'as owner', :nested_groups do @@ -443,7 +443,7 @@ describe API::Groups do it 'can create subgroups' do post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -455,7 +455,7 @@ describe API::Groups do it 'cannot create subgroups' do post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -466,7 +466,7 @@ describe API::Groups do post api("/groups", user3), group - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(group[:name]) expect(json_response["path"]).to eq(group[:path]) @@ -481,7 +481,7 @@ describe API::Groups do post api("/groups", user3), group - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}") expect(json_response["parent_id"]).to eq(parent.id) @@ -490,20 +490,20 @@ describe API::Groups do it "does not create group, duplicate" do post api("/groups", user3), { name: 'Duplicate Test', path: group2.path } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(response.message).to eq("Bad Request") end it "returns 400 bad request error if name not given" do post api("/groups", user3), { path: group2.path } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 400 bad request error if path not given" do post api("/groups", user3), { name: 'test' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -513,7 +513,7 @@ describe API::Groups do it "removes group" do delete api("/groups/#{group1.id}", user1) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it_behaves_like '412 response' do @@ -526,19 +526,19 @@ describe API::Groups do delete api("/groups/#{group1.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "does not remove a non existing group" do delete api("/groups/1328", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not remove a group not attached to user1" do delete api("/groups/#{group2.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -546,13 +546,13 @@ describe API::Groups do it "removes any existing group" do delete api("/groups/#{group2.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it "does not remove a non existing group" do delete api("/groups/1328", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -570,7 +570,7 @@ describe API::Groups do it "does not transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -578,7 +578,7 @@ describe API::Groups do it "transfers project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when using project path in URL' do @@ -586,7 +586,7 @@ describe API::Groups do it "transfers project to group" do post api("/groups/#{group1.id}/projects/#{project_path}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -594,7 +594,7 @@ describe API::Groups do it "does not transfer project to group" do post api("/groups/#{group1.id}/projects/nogroup%2Fnoproject", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -604,7 +604,7 @@ describe API::Groups do it "transfers project to group" do post api("/groups/#{group1.path}/projects/#{project_path}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -612,7 +612,7 @@ describe API::Groups do it "does not transfer project to group" do post api("/groups/noexist/projects/#{project_path}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index d4006fe71a2..6c0996c543d 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -1,4 +1,6 @@ require 'spec_helper' +require 'raven/transports/dummy' +require_relative '../../../config/initializers/sentry' describe API::Helpers do include API::APIGuard::HelperMethods @@ -26,39 +28,11 @@ describe API::Helpers do allow_any_instance_of(self.class).to receive(:options).and_return({}) end - def set_env(user_or_token, identifier) - clear_env - clear_param - env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token - env[API::Helpers::SUDO_HEADER] = identifier.to_s - end - - def set_param(user_or_token, identifier) - clear_env - clear_param - params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token - params[API::Helpers::SUDO_PARAM] = identifier.to_s - end - - def clear_env - env.delete(API::APIGuard::PRIVATE_TOKEN_HEADER) - env.delete(API::Helpers::SUDO_HEADER) - end - - def clear_param - params.delete(API::APIGuard::PRIVATE_TOKEN_PARAM) - params.delete(API::Helpers::SUDO_PARAM) - end - def warden_authenticate_returns(value) warden = double("warden", authenticate: value) env['warden'] = warden end - def doorkeeper_guard_returns(value) - allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value } - end - def error!(message, status, header) raise Exception.new("#{status} - #{message}") end @@ -67,10 +41,6 @@ describe API::Helpers do subject { current_user } describe "Warden authentication", :allow_forgery_protection do - before do - doorkeeper_guard_returns false - end - context "with invalid credentials" do context "GET request" do before do @@ -158,274 +128,53 @@ describe API::Helpers do end end - describe "when authenticating using a user's private token" do - it "returns nil for an invalid token" do - env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false } - - expect(current_user).to be_nil - end - - it "returns nil for a user without access" do - env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token - allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - - expect(current_user).to be_nil - end - - it "leaves user as is when sudo not specified" do - env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token - - expect(current_user).to eq(user) - - clear_env - - params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user.private_token - - expect(current_user).to eq(user) - end - end - describe "when authenticating using a user's personal access tokens" do let(:personal_access_token) { create(:personal_access_token, user: user) } - before do - allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false } - end - - it "returns nil for an invalid token" do + it "returns a 401 response for an invalid token" do env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' - expect(current_user).to be_nil + expect { current_user }.to raise_error /401/ end - it "returns nil for a user without access" do + it "returns a 403 response for a user without access" do env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - expect(current_user).to be_nil + expect { current_user }.to raise_error /403/ end - it "returns nil for a token without the appropriate scope" do - personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) + it 'returns a 403 response for a user who is blocked' do + user.block! env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token - expect(current_user).to be_nil + expect { current_user }.to raise_error /403/ end - it "leaves user as is when sudo not specified" do + it "sets current_user" do env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(current_user).to eq(user) - clear_env - params[API::APIGuard::PRIVATE_TOKEN_PARAM] = personal_access_token.token + end - expect(current_user).to eq(user) + it "does not allow tokens without the appropriate scope" do + personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token + + expect { current_user }.to raise_error API::APIGuard::InsufficientScopeError end it 'does not allow revoked tokens' do personal_access_token.revoke! env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token - expect(current_user).to be_nil + expect { current_user }.to raise_error API::APIGuard::RevokedError end it 'does not allow expired tokens' do personal_access_token.update_attributes!(expires_at: 1.day.ago) env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token - expect(current_user).to be_nil - end - end - - context 'sudo usage' do - context 'with admin' do - context 'with header' do - context 'with id' do - it 'changes current_user to sudo' do - set_env(admin, user.id) - - expect(current_user).to eq(user) - end - - it 'memoize the current_user: sudo permissions are not run against the sudoed user' do - set_env(admin, user.id) - - expect(current_user).to eq(user) - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_env(admin, admin.id) - - expect(current_user).to eq(admin) - end - - it 'throws an error when user cannot be found' do - id = user.id + admin.id - expect(user.id).not_to eq(id) - expect(admin.id).not_to eq(id) - - set_env(admin, id) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with username' do - it 'changes current_user to sudo' do - set_env(admin, user.username) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_env(admin, admin.username) - - expect(current_user).to eq(admin) - end - - it "throws an error when the user cannot be found for a given username" do - username = "#{user.username}#{admin.username}" - expect(user.username).not_to eq(username) - expect(admin.username).not_to eq(username) - - set_env(admin, username) - - expect { current_user }.to raise_error(Exception) - end - end - end - - context 'with param' do - context 'with id' do - it 'changes current_user to sudo' do - set_param(admin, user.id) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_param(admin, admin.id) - - expect(current_user).to eq(admin) - end - - it 'handles sudo to oneself using string' do - set_env(admin, user.id.to_s) - - expect(current_user).to eq(user) - end - - it 'throws an error when user cannot be found' do - id = user.id + admin.id - expect(user.id).not_to eq(id) - expect(admin.id).not_to eq(id) - - set_param(admin, id) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with username' do - it 'changes current_user to sudo' do - set_param(admin, user.username) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_param(admin, admin.username) - - expect(current_user).to eq(admin) - end - - it "throws an error when the user cannot be found for a given username" do - username = "#{user.username}#{admin.username}" - expect(user.username).not_to eq(username) - expect(admin.username).not_to eq(username) - - set_param(admin, username) - - expect { current_user }.to raise_error(Exception) - end - end - end - end - - context 'with regular user' do - context 'with env' do - it 'changes current_user to sudo when admin and user id' do - set_env(user, admin.id) - - expect { current_user }.to raise_error(Exception) - end - - it 'changes current_user to sudo when admin and user username' do - set_env(user, admin.username) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with params' do - it 'changes current_user to sudo when admin and user id' do - set_param(user, admin.id) - - expect { current_user }.to raise_error(Exception) - end - - it 'changes current_user to sudo when admin and user username' do - set_param(user, admin.username) - - expect { current_user }.to raise_error(Exception) - end - end - end - end - end - - describe '.sudo?' do - context 'when no sudo env or param is passed' do - before do - doorkeeper_guard_returns(nil) - end - - it 'returns false' do - expect(sudo?).to be_falsy - end - end - - context 'when sudo env or param is passed', 'user is not an admin' do - before do - set_env(user, '123') - end - - it 'returns an 403 Forbidden' do - expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Must be admin to use sudo"}' - end - end - - context 'when sudo env or param is passed', 'user is admin' do - context 'personal access token is used' do - before do - personal_access_token = create(:personal_access_token, user: admin) - set_env(personal_access_token.token, user.id) - end - - it 'returns an 403 Forbidden' do - expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Private token must be specified in order to use sudo"}' - end - end - - context 'private access token is used' do - before do - set_env(admin.private_token, user.id) - end - - it 'returns true' do - expect(sudo?).to be_truthy - end + expect { current_user }.to raise_error API::APIGuard::ExpiredError end end end @@ -450,10 +199,55 @@ describe API::Helpers do allow(exception).to receive(:backtrace).and_return(caller) expect_any_instance_of(self.class).to receive(:sentry_context) - expect(Raven).to receive(:capture_exception).with(exception) + expect(Raven).to receive(:capture_exception).with(exception, extra: {}) handle_api_exception(exception) end + + context 'with a personal access token given' do + let(:token) { create(:personal_access_token, scopes: ['api'], user: user) } + + # Regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/38571 + it 'does not raise an additional exception because of missing `request`' do + # We need to stub at a lower level than #sentry_enabled? otherwise + # Sentry is not enabled when the request below is made, and the test + # would pass even without the fix + expect(Gitlab::Sentry).to receive(:enabled?).twice.and_return(true) + expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!') + + get api('/projects', personal_access_token: token) + + # The 500 status is expected as we're testing a case where an exception + # is raised, but Grape shouldn't raise an additional exception + expect(response).to have_gitlab_http_status(500) + expect(json_response['message']).not_to include("undefined local variable or method `request'") + expect(json_response['message']).to start_with("\nRuntimeError (Runtime Error!):") + end + end + + context 'extra information' do + # Sentry events are an array of the form [auth_header, data, options] + let(:event_data) { Raven.client.transport.events.first[1] } + + before do + stub_application_setting( + sentry_enabled: true, + sentry_dsn: "dummy://12345:67890@sentry.localdomain/sentry/42" + ) + configure_sentry + Raven.client.configuration.encoding = 'json' + end + + it 'sends the params, excluding confidential values' do + expect(Gitlab::Sentry).to receive(:enabled?).twice.and_return(true) + expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!') + + get api('/projects', user), password: 'dont_send_this', other_param: 'send_this' + + expect(event_data).to include('other_param=send_this') + expect(event_data).to include('password=********') + end + end end describe '.authenticate_non_get!' do @@ -490,11 +284,10 @@ describe API::Helpers do context 'current_user is nil' do before do expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) - allow_any_instance_of(self.class).to receive(:initial_current_user).and_return(nil) end it 'returns a 401 response' do - expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + expect { authenticate! }.to raise_error /401/ end end @@ -502,34 +295,154 @@ describe API::Helpers do let(:user) { build(:user) } before do - expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user) - expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user) + expect_any_instance_of(self.class).to receive(:current_user).and_return(user) end it 'does not raise an error' do expect { authenticate! }.not_to raise_error end end + end - context 'current_user is blocked' do - let(:user) { build(:user, :blocked) } + context 'sudo' do + shared_examples 'successful sudo' do + it 'sets current_user' do + expect(current_user).to eq(user) + end + + it 'sets sudo?' do + expect(sudo?).to be_truthy + end + end + + shared_examples 'sudo' do + context 'when admin' do + before do + token.user = admin + token.save! + end + + context 'when token has sudo scope' do + before do + token.scopes = %w[sudo] + token.save! + end + + context 'when user exists' do + context 'when using header' do + context 'when providing username' do + before do + env[API::Helpers::SUDO_HEADER] = user.username + end + + it_behaves_like 'successful sudo' + end + + context 'when providing user ID' do + before do + env[API::Helpers::SUDO_HEADER] = user.id.to_s + end + + it_behaves_like 'successful sudo' + end + end + + context 'when using param' do + context 'when providing username' do + before do + params[API::Helpers::SUDO_PARAM] = user.username + end + + it_behaves_like 'successful sudo' + end + + context 'when providing user ID' do + before do + params[API::Helpers::SUDO_PARAM] = user.id.to_s + end + + it_behaves_like 'successful sudo' + end + end + end + + context 'when user does not exist' do + before do + params[API::Helpers::SUDO_PARAM] = 'nonexistent' + end + + it 'raises an error' do + expect { current_user }.to raise_error /User with ID or username 'nonexistent' Not Found/ + end + end + end + + context 'when token does not have sudo scope' do + before do + token.scopes = %w[api] + token.save! + + params[API::Helpers::SUDO_PARAM] = user.id.to_s + end + + it 'raises an error' do + expect { current_user }.to raise_error API::APIGuard::InsufficientScopeError + end + end + end + + context 'when not admin' do + before do + token.user = user + token.save! + + params[API::Helpers::SUDO_PARAM] = user.id.to_s + end + + it 'raises an error' do + expect { current_user }.to raise_error /Must be admin to use sudo/ + end + end + end + + context 'using an OAuth token' do + let(:token) { create(:oauth_access_token) } before do - expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user) + env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}" end - it 'raises an error' do - expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user) + it_behaves_like 'sudo' + end - expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + context 'using a personal access token' do + let(:token) { create(:personal_access_token) } + + context 'passed as param' do + before do + params[API::APIGuard::PRIVATE_TOKEN_PARAM] = token.token + end + + it_behaves_like 'sudo' end - it "doesn't raise an error if an admin user is impersonating a blocked user (via sudo)" do - admin_user = build(:user, :admin) + context 'passed as header' do + before do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = token.token + end - expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(admin_user) + it_behaves_like 'sudo' + end + end - expect { authenticate! }.not_to raise_error + context 'using warden authentication' do + before do + warden_authenticate_returns admin + env[API::Helpers::SUDO_HEADER] = user.username + end + + it 'raises an error' do + expect { current_user }.to raise_error /Must be authenticated using an OAuth or Personal Access Token to use sudo/ end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 1274e66bb4c..d919899282d 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -14,7 +14,7 @@ describe API::Internal do get api("/internal/check"), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['api_version']).to eq(API::API.version) expect(json_response['redis']).to be(true) end @@ -35,7 +35,7 @@ describe API::Internal do it 'returns one broadcast message' do get api('/internal/broadcast_message'), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['message']).to eq(broadcast_message.message) end end @@ -44,7 +44,7 @@ describe API::Internal do it 'returns nothing' do get api('/internal/broadcast_message'), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty end end @@ -55,7 +55,7 @@ describe API::Internal do get api('/internal/broadcast_message'), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty end end @@ -68,7 +68,7 @@ describe API::Internal do it 'returns active broadcast message(s)' do get api('/internal/broadcast_messages'), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response[0]['message']).to eq(broadcast_message.message) end end @@ -77,7 +77,7 @@ describe API::Internal do it 'returns nothing' do get api('/internal/broadcast_messages'), secret_token: secret_token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty end end @@ -154,7 +154,7 @@ describe API::Internal do it 'returns the correct information about the key' do lfs_auth(key.id, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq(user.username) expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token) @@ -164,7 +164,7 @@ describe API::Internal do it 'returns a 404 when the wrong key is provided' do lfs_auth(nil, project) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -174,7 +174,7 @@ describe API::Internal do it 'returns the correct information about the key' do lfs_auth(key.id, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token) expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) @@ -186,7 +186,7 @@ describe API::Internal do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(user.name) end @@ -214,7 +214,7 @@ describe API::Internal do GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar' }.to_json) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -222,7 +222,7 @@ describe API::Internal do it 'responds with success' do push(key, project.wiki) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("wiki-#{project.id}") @@ -234,7 +234,7 @@ describe API::Internal do it 'responds with success' do pull(key, project.wiki) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("wiki-#{project.id}") @@ -248,7 +248,7 @@ describe API::Internal do allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(false) pull(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -262,7 +262,7 @@ describe API::Internal do allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(true) pull(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -283,7 +283,7 @@ describe API::Internal do allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(false) push(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -297,7 +297,7 @@ describe API::Internal do allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(true) push(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -315,7 +315,7 @@ describe API::Internal do it do pull(key, project_with_repo_path('/' + project.full_path)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -326,7 +326,7 @@ describe API::Internal do it do pull(key, project_with_repo_path(project.full_path)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) expect(json_response["gl_repository"]).to eq("project-#{project.id}") @@ -344,7 +344,7 @@ describe API::Internal do it do pull(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey expect(user).not_to have_an_activity_record end @@ -354,7 +354,7 @@ describe API::Internal do it do push(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey expect(user).not_to have_an_activity_record end @@ -372,7 +372,7 @@ describe API::Internal do it do pull(key, personal_project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey expect(user).not_to have_an_activity_record end @@ -382,7 +382,7 @@ describe API::Internal do it do push(key, personal_project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey expect(user).not_to have_an_activity_record end @@ -399,7 +399,7 @@ describe API::Internal do it do pull(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -408,7 +408,7 @@ describe API::Internal do it do push(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -425,7 +425,7 @@ describe API::Internal do it do archive(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy end end @@ -434,7 +434,7 @@ describe API::Internal do it do archive(key, project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -444,7 +444,7 @@ describe API::Internal do it do pull(key, project_with_repo_path('gitlab/notexist')) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -453,7 +453,7 @@ describe API::Internal do it do pull(OpenStruct.new(id: 0), project) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_falsey end end @@ -535,7 +535,7 @@ describe API::Internal do it 'rejects the push' do push_with_path(key, old_path_to_repo) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to be_falsey expect(json_response['message']).to eq(project_moved_message) end @@ -543,7 +543,7 @@ describe API::Internal do it 'rejects the SSH pull' do pull_with_path(key, old_path_to_repo) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to be_falsey expect(json_response['message']).to eq(project_moved_message) end @@ -614,7 +614,7 @@ describe API::Internal do # # post api("/internal/notify_post_receive"), valid_params # - # expect(response).to have_http_status(200) + # expect(response).to have_gitlab_http_status(200) # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do @@ -626,7 +626,7 @@ describe API::Internal do # # post api("/internal/notify_post_receive"), valid_wiki_params # - # expect(response).to have_http_status(200) + # expect(response).to have_gitlab_http_status(200) # end # # it "returns 500 if the gitaly call fails" do @@ -635,7 +635,7 @@ describe API::Internal do # # post api("/internal/notify_post_receive"), valid_params # - # expect(response).to have_http_status(500) + # expect(response).to have_gitlab_http_status(500) # end # # context 'with a gl_repository parameter' do @@ -656,7 +656,7 @@ describe API::Internal do # # post api("/internal/notify_post_receive"), valid_params # - # expect(response).to have_http_status(200) + # expect(response).to have_gitlab_http_status(200) # end # # it "calls the Gitaly client with the wiki's repository if it's a wiki" do @@ -668,7 +668,7 @@ describe API::Internal do # # post api("/internal/notify_post_receive"), valid_wiki_params # - # expect(response).to have_http_status(200) + # expect(response).to have_gitlab_http_status(200) # end # end # end @@ -734,7 +734,7 @@ describe API::Internal do it 'returns one broadcast message' do post api("/internal/post_receive"), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(broadcast_message.message) end end @@ -743,7 +743,7 @@ describe API::Internal do it 'returns empty string' do post api("/internal/post_receive"), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) end end @@ -754,7 +754,7 @@ describe API::Internal do post api("/internal/post_receive"), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['broadcast_message']).to eq(nil) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 972e57bc373..99525cd0a6a 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -67,7 +67,7 @@ describe API::Issues, :mailer do it "returns authentication error" do get api("/issues") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context "when authenticated" do @@ -297,7 +297,7 @@ describe API::Issues, :mailer do it 'matches V4 response schema' do get api('/issues', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/issues') end end @@ -474,7 +474,7 @@ describe API::Issues, :mailer do it 'returns an array of issues with no milestone' do get api("#{base_url}?milestone=#{no_milestone_title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_confidential_issue.id) @@ -535,7 +535,7 @@ describe API::Issues, :mailer do it 'returns 404 when project does not exist' do get api('/projects/1000/issues', non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 on private projects for other users" do @@ -544,7 +544,7 @@ describe API::Issues, :mailer do get api("/projects/#{private_project.id}/issues", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns no issues when user has access to project but not issues' do @@ -732,7 +732,7 @@ describe API::Issues, :mailer do it 'exposes known attributes' do get api("/projects/#{project.id}/issues/#{issue.iid}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(issue.id) expect(json_response['iid']).to eq(issue.iid) expect(json_response['project_id']).to eq(issue.project.id) @@ -753,7 +753,7 @@ describe API::Issues, :mailer do it "exposes the 'closed_at' attribute" do get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['closed_at']).to be_present end @@ -773,39 +773,39 @@ describe API::Issues, :mailer do it "returns a project issue by internal id" do get api("/projects/#{project.id}/issues/#{issue.iid}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) end it "returns 404 if issue id not found" do get api("/projects/#{project.id}/issues/54321", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 if the issue ID is used" do get api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'confidential issues' do it "returns 404 for non project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 for project members with guest role" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns confidential issue for project members" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -813,7 +813,7 @@ describe API::Issues, :mailer do it "returns confidential issue for author" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -821,7 +821,7 @@ describe API::Issues, :mailer do it "returns confidential issue for assignee" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -829,7 +829,7 @@ describe API::Issues, :mailer do it "returns confidential issue for admin" do get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -842,7 +842,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', assignee_id: user2.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['assignee']['name']).to eq(user2.name) expect(json_response['assignees'].first['name']).to eq(user2.name) @@ -854,7 +854,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', assignee_ids: [user2.id, guest.id] - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['assignees'].count).to eq(1) end @@ -865,7 +865,7 @@ describe API::Issues, :mailer do title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['labels']).to eq(%w(label label2)) @@ -878,7 +878,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: true - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_truthy end @@ -887,7 +887,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: 'y' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_truthy end @@ -896,7 +896,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: false - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_falsy end @@ -905,7 +905,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') end @@ -923,7 +923,7 @@ describe API::Issues, :mailer do it "returns a 400 bad request if title not given" do post api("/projects/#{project.id}/issues", user), labels: 'label, label2' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'allows special label names' do @@ -941,7 +941,7 @@ describe API::Issues, :mailer do it 'returns 400 if title is too long' do post api("/projects/#{project.id}/issues", user), title: 'g' * 256 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -985,7 +985,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', due_date: due_date - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['due_date']).to eq(due_date) @@ -998,7 +998,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', created_at: creation_time - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end @@ -1028,7 +1028,7 @@ describe API::Issues, :mailer do it "does not create a new project issue" do expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -1044,7 +1044,7 @@ describe API::Issues, :mailer do it "updates a project issue" do put api("/projects/#{project.id}/issues/#{issue.iid}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -1052,13 +1052,13 @@ describe API::Issues, :mailer do it "returns 404 error if issue iid not found" do put api("/projects/#{project.id}/issues/44444", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 error if issue id is used instead of the iid" do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'allows special label names' do @@ -1078,33 +1078,33 @@ describe API::Issues, :mailer do it "returns 403 for non project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member), title: 'updated title' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns 403 for project members with guest role" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest), title: 'updated title' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "updates a confidential issue for project members" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end it "updates a confidential issue for author" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end it "updates a confidential issue for admin" do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -1112,7 +1112,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), confidential: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_truthy end @@ -1120,7 +1120,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), confidential: false - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_falsy end @@ -1128,7 +1128,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user), confidential: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') end end @@ -1149,7 +1149,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -1167,7 +1167,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_id: 0 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignee']).to be_nil end @@ -1176,7 +1176,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_id: user2.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignee']['name']).to eq(user2.name) end @@ -1186,7 +1186,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_ids: [0] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignees']).to be_empty end @@ -1195,7 +1195,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_ids: [user2.id] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignees'].first['name']).to eq(user2.name) end @@ -1205,7 +1205,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), assignee_ids: [user2.id, guest.id] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignees'].size).to eq(1) end @@ -1219,7 +1219,7 @@ describe API::Issues, :mailer do it 'does not update labels if not present' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to eq([label.title]) end @@ -1238,14 +1238,14 @@ describe API::Issues, :mailer do it 'removes all labels' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: '' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to eq([]) end it 'updates labels' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: 'foo,bar' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'foo' expect(json_response['labels']).to include 'bar' end @@ -1267,7 +1267,7 @@ describe API::Issues, :mailer do it 'returns 400 if title is too long' do put api("/projects/#{project.id}/issues/#{issue.iid}", user), title: 'g' * 256 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -1278,7 +1278,7 @@ describe API::Issues, :mailer do it "updates a project issue" do put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: 'label2', state_event: "close" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label2' expect(json_response['state']).to eq "closed" @@ -1287,7 +1287,7 @@ describe API::Issues, :mailer do it 'reopens a project isssue' do put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq 'opened' end @@ -1297,7 +1297,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end @@ -1310,7 +1310,7 @@ describe API::Issues, :mailer do put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to eq(due_date) end end @@ -1318,12 +1318,12 @@ describe API::Issues, :mailer do describe "DELETE /projects/:id/issues/:issue_iid" do it "rejects a non member from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "rejects a developer from deleting an issue" do delete api("/projects/#{project.id}/issues/#{issue.iid}", author) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context "when the user is project owner" do @@ -1333,7 +1333,7 @@ describe API::Issues, :mailer do it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.iid}", owner) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it_behaves_like '412 response' do @@ -1345,14 +1345,14 @@ describe API::Issues, :mailer do it 'returns 404 when trying to move an issue' do delete api("/projects/#{project.id}/issues/123", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end it 'returns 404 when using the issue ID instead of IID' do delete api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1364,7 +1364,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), to_project_id: target_project.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project.id) end @@ -1373,7 +1373,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), to_project_id: project.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue to project it originates from!') end end @@ -1383,7 +1383,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), to_project_id: target_project2.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') end end @@ -1392,7 +1392,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), to_project_id: target_project2.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project2.id) end @@ -1401,7 +1401,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Issue Not Found') end end @@ -1411,7 +1411,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/123/move", user), to_project_id: target_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Issue Not Found') end end @@ -1421,7 +1421,7 @@ describe API::Issues, :mailer do post api("/projects/123/issues/#{issue.iid}/move", user), to_project_id: target_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end end @@ -1431,7 +1431,7 @@ describe API::Issues, :mailer do post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), to_project_id: 123 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1440,32 +1440,32 @@ describe API::Issues, :mailer do it 'subscribes to an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the issue is not found' do post api("/projects/#{project.id}/issues/123/subscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if the issue ID is used instead of the iid' do post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if the issue is confidential' do post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/subscribe", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1473,32 +1473,32 @@ describe API::Issues, :mailer do it 'unsubscribes from an issue' do post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user2) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the issue is not found' do post api("/projects/#{project.id}/issues/123/unsubscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if using the issue ID instead of iid' do post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if the issue is confidential' do post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/unsubscribe", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1539,7 +1539,7 @@ describe API::Issues, :mailer do it "returns 404 when issue doesn't exists" do get api("/projects/#{project.id}/issues/9999/closed_by", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1549,7 +1549,7 @@ describe API::Issues, :mailer do it 'exposes known attributes' do get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) @@ -1558,12 +1558,12 @@ describe API::Issues, :mailer do it "returns unautorized for non-admin users" do get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end def expect_paginated_array_response(size: nil) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(size) if size diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 2d7cc1a1798..1765907c1b4 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -31,7 +31,7 @@ describe API::Jobs do context 'authorized user' do it 'returns project jobs' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array end @@ -55,7 +55,7 @@ describe API::Jobs do let(:query) { { 'scope' => 'pending' } } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -64,7 +64,7 @@ describe API::Jobs do let(:query) { { scope: %w(pending running) } } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -72,7 +72,7 @@ describe API::Jobs do context 'respond 400 when scope contains invalid state' do let(:query) { { scope: %w(unknown running) } } - it { expect(response).to have_http_status(400) } + it { expect(response).to have_gitlab_http_status(400) } end end @@ -80,7 +80,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not return project jobs' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -94,7 +94,7 @@ describe API::Jobs do context 'authorized user' do it 'returns pipeline jobs' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array end @@ -118,7 +118,7 @@ describe API::Jobs do let(:query) { { 'scope' => 'pending' } } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -127,7 +127,7 @@ describe API::Jobs do let(:query) { { scope: %w(pending running) } } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -135,7 +135,7 @@ describe API::Jobs do context 'respond 400 when scope contains invalid state' do let(:query) { { scope: %w(unknown running) } } - it { expect(response).to have_http_status(400) } + it { expect(response).to have_gitlab_http_status(400) } end context 'jobs in different pipelines' do @@ -152,7 +152,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not return jobs' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -164,8 +164,18 @@ describe API::Jobs do context 'authorized user' do it 'returns specific job data' do - expect(response).to have_http_status(200) - expect(json_response['name']).to eq('test') + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(job.id) + expect(json_response['status']).to eq(job.status) + expect(json_response['stage']).to eq(job.stage) + expect(json_response['name']).to eq(job.name) + expect(json_response['ref']).to eq(job.ref) + expect(json_response['tag']).to eq(job.tag) + expect(json_response['coverage']).to eq(job.coverage) + expect(Time.parse(json_response['created_at'])).to be_like_time(job.created_at) + expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at) + expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) + expect(json_response['duration']).to eq(job.duration) end it 'returns pipeline data' do @@ -183,7 +193,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not return specific job data' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -207,7 +217,7 @@ describe API::Jobs do get_artifact_file(artifact) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -219,7 +229,7 @@ describe API::Jobs do get_artifact_file(artifact) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -231,7 +241,7 @@ describe API::Jobs do get_artifact_file(artifact) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -244,7 +254,7 @@ describe API::Jobs do get_artifact_file(artifact) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers) .to include('Content-Type' => 'application/json', 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/) @@ -256,7 +266,7 @@ describe API::Jobs do it 'does not return job artifact file' do get_artifact_file('some/artifact') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -281,7 +291,7 @@ describe API::Jobs do end it 'returns specific job artifacts' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers).to include(download_headers) expect(response.body).to match_file(job.artifacts_file.file.file) end @@ -292,13 +302,13 @@ describe API::Jobs do it 'hides artifacts and rejects request' do expect(project).to be_private - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end it 'does not return job artifacts if not uploaded' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -323,7 +333,7 @@ describe API::Jobs do it 'does not find a resource in a private project' do expect(project).to be_private - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -335,13 +345,13 @@ describe API::Jobs do end it 'gives 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'non-existing job' do shared_examples 'not found' do - it { expect(response).to have_http_status(:not_found) } + it { expect(response).to have_gitlab_http_status(:not_found) } end context 'has no such ref' do @@ -369,7 +379,7 @@ describe API::Jobs do "attachment; filename=#{job.artifacts_file.filename}" } end - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } it { expect(response.headers).to include(download_headers) } end @@ -410,7 +420,7 @@ describe API::Jobs do context 'authorized user' do it 'returns specific job trace' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.body).to eq(job.trace.raw) end end @@ -419,7 +429,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not return specific job trace' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -432,7 +442,7 @@ describe API::Jobs do context 'authorized user' do context 'user with :update_build persmission' do it 'cancels running or pending job' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(project.builds.first.status).to eq('canceled') end end @@ -441,7 +451,7 @@ describe API::Jobs do let(:api_user) { reporter } it 'does not cancel job' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -450,7 +460,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not cancel job' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -465,7 +475,7 @@ describe API::Jobs do context 'authorized user' do context 'user with :update_build permission' do it 'retries non-running job' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') end @@ -475,7 +485,7 @@ describe API::Jobs do let(:api_user) { reporter } it 'does not retry job' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -484,7 +494,7 @@ describe API::Jobs do let(:api_user) { nil } it 'does not retry job' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -498,7 +508,7 @@ describe API::Jobs do let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } it 'erases job content' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(job).not_to have_trace expect(job.artifacts_file.exists?).to be_falsy expect(job.artifacts_metadata.exists?).to be_falsy @@ -516,7 +526,7 @@ describe API::Jobs do let(:job) { create(:ci_build, :trace, project: project, pipeline: pipeline) } it 'responds with forbidden' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -533,7 +543,7 @@ describe API::Jobs do end it 'keeps artifacts' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(job.reload.artifacts_expire_at).to be_nil end end @@ -542,7 +552,7 @@ describe API::Jobs do let(:job) { create(:ci_build, project: project, pipeline: pipeline) } it 'responds with not found' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -557,7 +567,7 @@ describe API::Jobs do context 'when user is authorized to trigger a manual action' do it 'plays the job' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['user']['id']).to eq(user.id) expect(json_response['id']).to eq(job.id) expect(job.reload).to be_pending @@ -570,7 +580,7 @@ describe API::Jobs do it 'does not trigger a manual action' do expect(job.reload).to be_manual - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -579,7 +589,7 @@ describe API::Jobs do it 'does not trigger a manual action' do expect(job.reload).to be_manual - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -587,7 +597,7 @@ describe API::Jobs do context 'on a non-playable job' do it 'returns a status code 400, Bad Request' do - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status 400 expect(response.body).to match("Unplayable Job") end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index f534332ca6c..3c4719964b6 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -10,14 +10,14 @@ describe API::Keys do context 'when unauthenticated' do it 'returns authentication error' do get api("/keys/#{key.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated' do it 'returns 404 for non-existing key' do get api('/keys/999999', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -25,7 +25,7 @@ describe API::Keys do user.keys << key user.save get api("/keys/#{key.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(key.title) expect(json_response['user']['id']).to eq(user.id) expect(json_response['user']['username']).to eq(user.username) diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b231fdea2a3..3498e5bc8d9 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -27,7 +27,7 @@ describe API::Labels do get api("/projects/#{project.id}/labels", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(3) @@ -75,7 +75,7 @@ describe API::Labels do description: 'test', priority: 2 - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('Foo') expect(json_response['color']).to eq('#FFAABB') expect(json_response['description']).to eq('test') @@ -109,19 +109,19 @@ describe API::Labels do it 'returns a 400 bad request if name not given' do post api("/projects/#{project.id}/labels", user), color: '#FFAABB' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 bad request if color not given' do post api("/projects/#{project.id}/labels", user), name: 'Foobar' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 for invalid color' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAA' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -129,7 +129,7 @@ describe API::Labels do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -137,7 +137,7 @@ describe API::Labels do post api("/projects/#{project.id}/labels", user), name: ',', color: '#FFAABB' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -150,7 +150,7 @@ describe API::Labels do name: group_label.name, color: '#FFAABB' - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Label already exists') end @@ -160,14 +160,14 @@ describe API::Labels do color: '#FFAAFFFF', priority: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 409 if label already exists in project' do post api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFAABB' - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Label already exists') end end @@ -176,18 +176,18 @@ describe API::Labels do it 'returns 204 for existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it 'returns 404 for non existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label2' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Label Not Found') end it 'returns 400 for wrong parameters' do delete api("/projects/#{project.id}/labels", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it_behaves_like '412 response' do @@ -203,7 +203,7 @@ describe API::Labels do new_name: 'New Label', color: '#FFFFFF', description: 'test' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq('#FFFFFF') expect(json_response['description']).to eq('test') @@ -213,7 +213,7 @@ describe API::Labels do put api("/projects/#{project.id}/labels", user), name: 'label1', new_name: 'New Label' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('New Label') expect(json_response['color']).to eq(label1.color) end @@ -222,7 +222,7 @@ describe API::Labels do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFFFFF' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(label1.name) expect(json_response['color']).to eq('#FFFFFF') end @@ -232,7 +232,7 @@ describe API::Labels do name: 'bug', description: 'test' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(priority_label.name) expect(json_response['description']).to eq('test') expect(json_response['priority']).to eq(3) @@ -272,18 +272,18 @@ describe API::Labels do put api("/projects/#{project.id}/labels", user), name: 'label2', new_name: 'label3' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 400 if no label name given' do put api("/projects/#{project.id}/labels", user), new_name: 'label2' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('name is missing') end it 'returns 400 if no new parameters given' do put api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\ 'at least one parameter must be provided') end @@ -293,7 +293,7 @@ describe API::Labels do name: 'label1', new_name: ',', color: '#FFFFFF' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq(['is invalid']) end @@ -301,7 +301,7 @@ describe API::Labels do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FF' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -309,7 +309,7 @@ describe API::Labels do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['color']).to eq(['must be a valid color code']) end @@ -318,7 +318,7 @@ describe API::Labels do name: 'Foo', priority: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -327,7 +327,7 @@ describe API::Labels do it "subscribes to the label" do post api("/projects/#{project.id}/labels/#{label1.title}/subscribe", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -337,7 +337,7 @@ describe API::Labels do it "subscribes to the label" do post api("/projects/#{project.id}/labels/#{label1.id}/subscribe", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -351,7 +351,7 @@ describe API::Labels do it "returns 304" do post api("/projects/#{project.id}/labels/#{label1.id}/subscribe", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end @@ -359,7 +359,7 @@ describe API::Labels do it "returns 404 error" do post api("/projects/#{project.id}/labels/1234/subscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -373,7 +373,7 @@ describe API::Labels do it "unsubscribes from the label" do post api("/projects/#{project.id}/labels/#{label1.title}/unsubscribe", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -383,7 +383,7 @@ describe API::Labels do it "unsubscribes from the label" do post api("/projects/#{project.id}/labels/#{label1.id}/unsubscribe", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -397,7 +397,7 @@ describe API::Labels do it "returns 304" do post api("/projects/#{project.id}/labels/#{label1.id}/unsubscribe", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end @@ -405,7 +405,7 @@ describe API::Labels do it "returns 404 error" do post api("/projects/#{project.id}/labels/1234/unsubscribe", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index df7c91b5bc1..e3065840e6f 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -10,7 +10,7 @@ describe API::Lint do it 'passes validation' do post api('/ci/lint'), { content: yaml_content } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Hash expect(json_response['status']).to eq('valid') expect(json_response['errors']).to eq([]) @@ -21,7 +21,7 @@ describe API::Lint do it 'responds with errors about invalid syntax' do post api('/ci/lint'), { content: 'invalid content' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('invalid') expect(json_response['errors']).to eq(['Invalid configuration format']) end @@ -29,7 +29,7 @@ describe API::Lint do it "responds with errors about invalid configuration" do post api('/ci/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('invalid') expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) end @@ -39,7 +39,7 @@ describe API::Lint do it 'responds with validation error about missing content' do post api('/ci/lint') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('content is missing') end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index d3bae8d2888..3349e396ab8 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -35,7 +35,7 @@ describe API::Members do get api("/#{source_type.pluralize}/#{source.id}/members", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) @@ -49,7 +49,7 @@ describe API::Members do get api("/#{source_type.pluralize}/#{source.id}/members", developer) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) @@ -59,7 +59,7 @@ describe API::Members do it 'finds members with query string' do get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.count).to eq(1) @@ -81,7 +81,7 @@ describe API::Members do user = public_send(type) get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) # User attributes expect(json_response['id']).to eq(developer.id) expect(json_response['name']).to eq(developer.name) @@ -116,7 +116,7 @@ describe API::Members do post api("/#{source_type.pluralize}/#{source.id}/members", user), user_id: access_requester.id, access_level: Member::MASTER - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -129,7 +129,7 @@ describe API::Members do post api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: access_requester.id, access_level: Member::MASTER - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) expect(source.requesters.count).to eq(0) expect(json_response['id']).to eq(access_requester.id) @@ -142,7 +142,7 @@ describe API::Members do post api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) expect(json_response['id']).to eq(stranger.id) expect(json_response['access_level']).to eq(Member::DEVELOPER) @@ -154,28 +154,28 @@ describe API::Members do post api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: master.id, access_level: Member::MASTER - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) end it 'returns 400 when user_id is not given' do post api("/#{source_type.pluralize}/#{source.id}/members", master), access_level: Member::MASTER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access_level is not given' do post api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access_level is not valid' do post api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id, access_level: 1234 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -197,7 +197,7 @@ describe API::Members do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user), access_level: Member::MASTER - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -208,7 +208,7 @@ describe API::Members do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), access_level: Member::MASTER, expires_at: '2016-08-05' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(developer.id) expect(json_response['access_level']).to eq(Member::MASTER) expect(json_response['expires_at']).to eq('2016-08-05') @@ -219,20 +219,20 @@ describe API::Members do put api("/#{source_type.pluralize}/#{source.id}/members/123", master), access_level: Member::MASTER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 400 when access_level is not given' do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access level is not valid' do put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), access_level: 1234 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -250,7 +250,7 @@ describe API::Members do user = public_send(type) delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -261,7 +261,7 @@ describe API::Members do expect do delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { source.members.count }.by(-1) end end @@ -272,7 +272,7 @@ describe API::Members do expect do delete api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end.not_to change { source.requesters.count } end end @@ -281,7 +281,7 @@ describe API::Members do expect do delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { source.members.count }.by(-1) end @@ -293,7 +293,7 @@ describe API::Members do it 'returns 404 if member does not exist' do delete api("/#{source_type.pluralize}/#{source.id}/members/123", master) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -344,7 +344,7 @@ describe API::Members do post api("/projects/#{project.id}/members", master), user_id: stranger.id, access_level: Member::OWNER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end.to change { project.members.count }.by(0) end end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index d9da94d4713..bf4c8443b23 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -26,12 +26,12 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do it 'returns a 404 when merge_request id is used instead of the iid' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 when merge_request_iid not found' do get api("/projects/#{project.id}/merge_requests/999/versions", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -49,17 +49,17 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do it 'returns a 404 when merge_request id is used instead of the iid' do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 when merge_request version_id is not found' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 when merge_request_iid is not found' do get api("/projects/#{project.id}/merge_requests/12345/versions/#{merge_request_diff.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index c4f6e97b915..024cfe8b372 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1,6 +1,8 @@ require "spec_helper" describe API::MergeRequests do + include ProjectForksHelper + let(:base_time) { Time.now } let(:user) { create(:user) } let(:admin) { create(:user, :admin) } @@ -31,26 +33,26 @@ describe API::MergeRequests do it 'returns an array of all merge requests' do get api('/merge_requests', user), scope: 'all' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end it "returns authentication error without any scope" do get api("/merge_requests") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns authentication error when scope is assigned-to-me" do get api("/merge_requests"), scope: 'assigned-to-me' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns authentication error when scope is created-by-me" do get api("/merge_requests"), scope: 'created-by-me' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -156,7 +158,7 @@ describe API::MergeRequests do it 'returns merge requests for public projects' do get api("/projects/#{project.id}/merge_requests") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end @@ -164,7 +166,7 @@ describe API::MergeRequests do project = create(:project, :private) get api("/projects/#{project.id}/merge_requests") - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -616,17 +618,17 @@ describe API::MergeRequests do context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:forked_project) { fork_project(project, user2) } let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before do - fork_project.add_reporter(user2) + forked_project.add_reporter(user2) allow_any_instance_of(MergeRequest).to receive(:write_ref) end it "returns merge_request" do - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' expect(response).to have_gitlab_http_status(201) @@ -635,10 +637,10 @@ describe API::MergeRequests do end it "does not return 422 when source_branch equals target_branch" do - expect(project.id).not_to eq(fork_project.id) - expect(fork_project.forked?).to be_truthy - expect(fork_project.forked_from_project).to eq(project) - post api("/projects/#{fork_project.id}/merge_requests", user2), + expect(project.id).not_to eq(forked_project.id) + expect(forked_project.forked?).to be_truthy + expect(forked_project.forked_from_project).to eq(project) + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') @@ -647,7 +649,7 @@ describe API::MergeRequests do it 'returns 422 when target project has disabled merge requests' do project.project_feature.update(merge_requests_access_level: 0) - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test', target_branch: 'master', source_branch: 'markdown', @@ -658,36 +660,26 @@ describe API::MergeRequests do end it "returns 400 when source_branch is missing" do - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end it "returns 400 when target_branch is missing" do - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end it "returns 400 when title is missing" do - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end context 'when target_branch is specified' do - it 'returns 422 if not a forked project' do - post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - target_branch: 'master', - source_branch: 'markdown', - author: user, - target_project_id: fork_project.id - expect(response).to have_gitlab_http_status(422) - end - it 'returns 422 if targeting a different fork' do - post api("/projects/#{fork_project.id}/merge_requests", user2), + post api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', @@ -698,8 +690,8 @@ describe API::MergeRequests do end it "returns 201 when target_branch is specified and for the same project" do - post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id + post api("/projects/#{forked_project.id}/merge_requests", user2), + title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id expect(response).to have_gitlab_http_status(201) end end @@ -1069,6 +1061,30 @@ describe API::MergeRequests do end end + describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do + before do + ::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request) + end + + it 'removes the merge_when_pipeline_succeeds status' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/cancel_merge_when_pipeline_succeeds", user) + + expect(response).to have_gitlab_http_status(201) + end + + it 'returns 404 if the merge request is not found' do + post api("/projects/#{project.id}/merge_requests/123/merge_when_pipeline_succeeds", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 if the merge request id is used instead of iid' do + post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge_when_pipeline_succeeds", user) + + expect(response).to have_gitlab_http_status(404) + end + end + describe 'Time tracking' do let(:issuable) { merge_request } diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 26cf653ca8e..e60716d46d7 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -10,7 +10,7 @@ describe API::Namespaces do context "when unauthenticated" do it "returns authentication error" do get api("/namespaces") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -21,7 +21,7 @@ describe API::Namespaces do group_kind_json_response = json_response.find { |resource| resource['kind'] == 'group' } user_kind_json_response = json_response.find { |resource| resource['kind'] == 'user' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(group_kind_json_response.keys).to contain_exactly('id', 'kind', 'name', 'path', 'full_path', 'parent_id', 'members_count_with_descendants') @@ -32,7 +32,7 @@ describe API::Namespaces do it "admin: returns an array of all namespaces" do get api("/namespaces", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(Namespace.count) @@ -41,7 +41,7 @@ describe API::Namespaces do it "admin: returns an array of matched namespaces" do get api("/namespaces?search=#{group2.name}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -75,7 +75,7 @@ describe API::Namespaces do it "user: returns an array of namespaces" do get api("/namespaces", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -84,7 +84,7 @@ describe API::Namespaces do it "admin: returns an array of matched namespaces" do get api("/namespaces?search=#{user.username}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index f5882c0c74a..784070db173 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -37,7 +37,7 @@ describe API::Notes do it "returns an array of issue notes" do get api("/projects/#{project.id}/issues/#{issue.iid}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(issue_note.note) @@ -46,14 +46,14 @@ describe API::Notes do it "returns a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/12345/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "and current user cannot view the notes" do it "returns an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response).to be_empty @@ -67,7 +67,7 @@ describe API::Notes do it "returns 404" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -75,7 +75,7 @@ describe API::Notes do it "returns an empty array" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes", private_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(cross_reference_note.note) @@ -88,7 +88,7 @@ describe API::Notes do it "returns an array of snippet notes" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(snippet_note.note) @@ -97,13 +97,13 @@ describe API::Notes do it "returns a 404 error when snippet id not found" do get api("/projects/#{project.id}/snippets/42/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -111,7 +111,7 @@ describe API::Notes do it "returns an array of merge_requests notes" do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(merge_request_note.note) @@ -120,13 +120,13 @@ describe API::Notes do it "returns a 404 error if merge request id not found" do get api("/projects/#{project.id}/merge_requests/4444/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 when not authorized" do get api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -136,21 +136,21 @@ describe API::Notes do it "returns an issue note by id" do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(issue_note.note) end it "returns a 404 error if issue note not found" do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "and current user cannot view the note" do it "returns a 404 error" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "when issue is confidential" do @@ -161,7 +161,7 @@ describe API::Notes do it "returns 404" do get api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{issue_note.id}", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -169,7 +169,7 @@ describe API::Notes do it "returns an issue note by id" do get api("/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes/#{cross_reference_note.id}", private_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(cross_reference_note.note) end end @@ -180,14 +180,14 @@ describe API::Notes do it "returns a snippet note by id" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(snippet_note.note) end it "returns a 404 error if snippet note not found" do get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -197,7 +197,7 @@ describe API::Notes do it "creates a new issue note" do post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -205,13 +205,13 @@ describe API::Notes do it "returns a 400 bad request error if body not given" do post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/issues/#{issue.iid}/notes"), body: 'hi!' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end context 'when an admin or owner makes the request' do @@ -220,7 +220,7 @@ describe API::Notes do post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'hi!', created_at: creation_time - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -233,7 +233,7 @@ describe API::Notes do it 'creates a new issue note' do post api("/projects/#{project.id}/issues/#{issue2.iid}/notes", user), body: ':+1:' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') end end @@ -242,7 +242,7 @@ describe API::Notes do it 'creates a new issue note' do post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: ':+1:' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') end end @@ -252,7 +252,7 @@ describe API::Notes do it "creates a new snippet note" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -260,13 +260,13 @@ describe API::Notes do it "returns a 400 bad request error if body not given" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if user not authenticated" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -278,7 +278,7 @@ describe API::Notes do post api("/projects/#{project.id}/issues/#{issue.iid}/notes", user), body: 'Foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -302,6 +302,40 @@ describe API::Notes do expect(private_issue.notes.reload).to be_empty end end + + context 'when the merge request discussion is locked' do + before do + merge_request.update_attribute(:discussion_locked, true) + end + + context 'when a user is a team member' do + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), body: 'Hi!' } + + it 'returns 200 status' do + subject + + expect(response).to have_gitlab_http_status(201) + end + + it 'creates a new note' do + expect { subject }.to change { Note.count }.by(1) + end + end + + context 'when a user is not a team member' do + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", private_user), body: 'Hi!' } + + it 'returns 403 status' do + subject + + expect(response).to have_gitlab_http_status(403) + end + + it 'does not create a new note' do + expect { subject }.not_to change { Note.count } + end + end + end end describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do @@ -318,7 +352,7 @@ describe API::Notes do put api("/projects/#{project.id}/issues/#{issue.iid}/"\ "notes/#{issue_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -326,14 +360,14 @@ describe API::Notes do put api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user), body: 'Hello!' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 400 bad request error if body not given' do put api("/projects/#{project.id}/issues/#{issue.iid}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -342,7 +376,7 @@ describe API::Notes do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -350,7 +384,7 @@ describe API::Notes do put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user), body: "Hello!" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -359,7 +393,7 @@ describe API::Notes do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\ "notes/#{merge_request_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -367,7 +401,7 @@ describe API::Notes do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/"\ "notes/12345", user), body: "Hello!" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -378,17 +412,17 @@ describe API::Notes do delete api("/projects/#{project.id}/issues/#{issue.iid}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) # Check if note is really deleted delete api("/projects/#{project.id}/issues/#{issue.iid}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/issues/#{issue.iid}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -401,18 +435,18 @@ describe API::Notes do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) # Check if note is really deleted delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -425,18 +459,18 @@ describe API::Notes do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.iid}/notes/#{merge_request_note.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) # Check if note is really deleted delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.iid}/notes/#{merge_request_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.iid}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index 7968659a1ec..3273cd26690 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -9,7 +9,7 @@ describe API::NotificationSettings do it "returns global notification settings for the current user" do get api("/notification_settings", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash expect(json_response['notification_email']).to eq(user.notification_email) expect(json_response['level']).to eq(user.global_notification_setting.level) @@ -22,7 +22,7 @@ describe API::NotificationSettings do it "updates global notification settings for the current user" do put api("/notification_settings", user), { level: 'watch', notification_email: email.email } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['notification_email']).to eq(email.email) expect(user.reload.notification_email).to eq(email.email) expect(json_response['level']).to eq(user.reload.global_notification_setting.level) @@ -33,7 +33,7 @@ describe API::NotificationSettings do it "fails on non-user email address" do put api("/notification_settings", user), { notification_email: 'invalid@example.com' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -41,7 +41,7 @@ describe API::NotificationSettings do it "returns group level notification settings for the current user" do get api("/groups/#{group.id}/notification_settings", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash expect(json_response['level']).to eq(user.notification_settings_for(group).level) end @@ -51,7 +51,7 @@ describe API::NotificationSettings do it "updates group level notification settings for the current user" do put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level) end end @@ -60,7 +60,7 @@ describe API::NotificationSettings do it "returns project level notification settings for the current user" do get api("/projects/#{project.id}/notification_settings", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash expect(json_response['level']).to eq(user.notification_settings_for(project).level) end @@ -70,7 +70,7 @@ describe API::NotificationSettings do it "updates project level notification settings for the current user" do put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level) expect(json_response['events']['new_note']).to be_truthy expect(json_response['events']['new_issue']).to be_falsey @@ -81,7 +81,7 @@ describe API::NotificationSettings do it "fails on invalid level" do put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index 0d56e1f732e..bdda80cc229 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -12,7 +12,7 @@ describe 'OAuth tokens' do request_oauth_token(user) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(json_response['error']).to eq('invalid_grant') end end @@ -23,7 +23,7 @@ describe 'OAuth tokens' do request_oauth_token(user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['access_token']).not_to be_nil end end @@ -35,7 +35,7 @@ describe 'OAuth tokens' do request_oauth_token(user) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -46,7 +46,7 @@ describe 'OAuth tokens' do request_oauth_token(user) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb new file mode 100644 index 00000000000..d13b3a958c9 --- /dev/null +++ b/spec/requests/api/pages_domains_spec.rb @@ -0,0 +1,440 @@ +require 'rails_helper' + +describe API::PagesDomains do + set(:project) { create(:project) } + set(:user) { create(:user) } + + set(:pages_domain) { create(:pages_domain, domain: 'www.domain.test', project: project) } + set(:pages_domain_secure) { create(:pages_domain, :with_certificate, :with_key, domain: 'ssl.domain.test', project: project) } + set(:pages_domain_expired) { create(:pages_domain, :with_expired_certificate, :with_key, domain: 'expired.domain.test', project: project) } + + let(:pages_domain_params) { build(:pages_domain, domain: 'www.other-domain.test').slice(:domain) } + let(:pages_domain_secure_params) { build(:pages_domain, :with_certificate, :with_key, domain: 'ssl.other-domain.test', project: project).slice(:domain, :certificate, :key) } + let(:pages_domain_secure_key_missmatch_params) {build(:pages_domain, :with_trusted_chain, :with_key, project: project).slice(:domain, :certificate, :key) } + let(:pages_domain_secure_missing_chain_params) {build(:pages_domain, :with_missing_chain, project: project).slice(:certificate) } + + let(:route) { "/projects/#{project.id}/pages/domains" } + let(:route_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain.domain}" } + let(:route_secure_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_secure.domain}" } + let(:route_expired_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_expired.domain}" } + let(:route_vacant_domain) { "/projects/#{project.id}/pages/domains/www.vacant-domain.test" } + + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + end + + describe 'GET /projects/:project_id/pages/domains' do + shared_examples_for 'get pages domains' do + it 'returns paginated pages domains' do + get api(route, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(3) + expect(json_response.map { |pages_domain| pages_domain['domain'] }).to include(pages_domain.domain) + expect(json_response.last).to have_key('domain') + end + end + + context 'when pages is disabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) + project.add_master(user) + end + + it_behaves_like '404 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it_behaves_like 'get pages domains' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is not a member' do + it_behaves_like '404 response' do + let(:request) { get api(route, user) } + end + end + end + + describe 'GET /projects/:project_id/pages/domains/:domain' do + shared_examples_for 'get pages domain' do + it 'returns pages domain' do + get api(route_domain, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['domain']).to eq(pages_domain.domain) + expect(json_response['url']).to eq(pages_domain.url) + expect(json_response['certificate']).to be_nil + end + + it 'returns pages domain with a certificate' do + get api(route_secure_domain, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['domain']).to eq(pages_domain_secure.domain) + expect(json_response['url']).to eq(pages_domain_secure.url) + expect(json_response['certificate']['subject']).to eq(pages_domain_secure.subject) + expect(json_response['certificate']['expired']).to be false + end + + it 'returns pages domain with an expired certificate' do + get api(route_expired_domain, user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['certificate']['expired']).to be true + end + end + + context 'when domain is vacant' do + before do + project.add_master(user) + end + + it_behaves_like '404 response' do + let(:request) { get api(route_vacant_domain, user) } + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it_behaves_like 'get pages domain' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { get api(route, user) } + end + end + + context 'when user is not a member' do + it_behaves_like '404 response' do + let(:request) { get api(route, user) } + end + end + end + + describe 'POST /projects/:project_id/pages/domains' do + let(:params) { pages_domain_params.slice(:domain) } + let(:params_secure) { pages_domain_secure_params.slice(:domain, :certificate, :key) } + + shared_examples_for 'post pages domains' do + it 'creates a new pages domain' do + post api(route, user), params + pages_domain = PagesDomain.find_by(domain: json_response['domain']) + + expect(response).to have_gitlab_http_status(201) + expect(pages_domain.domain).to eq(params[:domain]) + expect(pages_domain.certificate).to be_nil + expect(pages_domain.key).to be_nil + end + + it 'creates a new secure pages domain' do + post api(route, user), params_secure + pages_domain = PagesDomain.find_by(domain: json_response['domain']) + + expect(response).to have_gitlab_http_status(201) + expect(pages_domain.domain).to eq(params_secure[:domain]) + expect(pages_domain.certificate).to eq(params_secure[:certificate]) + expect(pages_domain.key).to eq(params_secure[:key]) + end + + it 'fails to create pages domain without key' do + post api(route, user), pages_domain_secure_params.slice(:domain, :certificate) + + expect(response).to have_gitlab_http_status(400) + end + + it 'fails to create pages domain with key missmatch' do + post api(route, user), pages_domain_secure_key_missmatch_params.slice(:domain, :certificate, :key) + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it_behaves_like 'post pages domains' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like '403 response' do + let(:request) { post api(route, user), params } + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like '403 response' do + let(:request) { post api(route, user), params } + end + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { post api(route, user), params } + end + end + + context 'when user is not a member' do + it_behaves_like '404 response' do + let(:request) { post api(route, user), params } + end + end + end + + describe 'PUT /projects/:project_id/pages/domains/:domain' do + let(:params_secure) { pages_domain_secure_params.slice(:certificate, :key) } + let(:params_secure_nokey) { pages_domain_secure_params.slice(:certificate) } + + shared_examples_for 'put pages domain' do + it 'updates pages domain removing certificate' do + put api(route_secure_domain, user) + pages_domain_secure.reload + + expect(response).to have_gitlab_http_status(200) + expect(pages_domain_secure.certificate).to be_nil + expect(pages_domain_secure.key).to be_nil + end + + it 'updates pages domain adding certificate' do + put api(route_domain, user), params_secure + pages_domain.reload + + expect(response).to have_gitlab_http_status(200) + expect(pages_domain.certificate).to eq(params_secure[:certificate]) + expect(pages_domain.key).to eq(params_secure[:key]) + end + + it 'updates pages domain with expired certificate' do + put api(route_expired_domain, user), params_secure + pages_domain_expired.reload + + expect(response).to have_gitlab_http_status(200) + expect(pages_domain_expired.certificate).to eq(params_secure[:certificate]) + expect(pages_domain_expired.key).to eq(params_secure[:key]) + end + + it 'updates pages domain with expired certificate not updating key' do + put api(route_secure_domain, user), params_secure_nokey + pages_domain_secure.reload + + expect(response).to have_gitlab_http_status(200) + expect(pages_domain_secure.certificate).to eq(params_secure_nokey[:certificate]) + end + + it 'fails to update pages domain adding certificate without key' do + put api(route_domain, user), params_secure_nokey + + expect(response).to have_gitlab_http_status(400) + end + + it 'fails to update pages domain adding certificate with missing chain' do + put api(route_domain, user), pages_domain_secure_missing_chain_params.slice(:certificate) + + expect(response).to have_gitlab_http_status(400) + end + + it 'fails to update pages domain with key missmatch' do + put api(route_secure_domain, user), pages_domain_secure_key_missmatch_params.slice(:certificate, :key) + + expect(response).to have_gitlab_http_status(400) + end + end + + context 'when domain is vacant' do + before do + project.add_master(user) + end + + it_behaves_like '404 response' do + let(:request) { put api(route_vacant_domain, user) } + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it_behaves_like 'put pages domain' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like '403 response' do + let(:request) { put api(route_domain, user) } + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like '403 response' do + let(:request) { put api(route_domain, user) } + end + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { put api(route_domain, user) } + end + end + + context 'when user is not a member' do + it_behaves_like '404 response' do + let(:request) { put api(route_domain, user) } + end + end + end + + describe 'DELETE /projects/:project_id/pages/domains/:domain' do + shared_examples_for 'delete pages domain' do + it 'deletes a pages domain' do + delete api(route_domain, user) + + expect(response).to have_gitlab_http_status(204) + end + end + + context 'when domain is vacant' do + before do + project.add_master(user) + end + + it_behaves_like '404 response' do + let(:request) { delete api(route_vacant_domain, user) } + end + end + + context 'when user is a master' do + before do + project.add_master(user) + end + + it_behaves_like 'delete pages domain' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like '403 response' do + let(:request) { delete api(route_domain, user) } + end + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like '403 response' do + let(:request) { delete api(route_domain, user) } + end + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like '403 response' do + let(:request) { delete api(route_domain, user) } + end + end + + context 'when user is not a member' do + it_behaves_like '404 response' do + let(:request) { delete api(route_domain, user) } + end + end + end +end diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb index f650df57383..7ea25059756 100644 --- a/spec/requests/api/pipeline_schedules_spec.rb +++ b/spec/requests/api/pipeline_schedules_spec.rb @@ -20,7 +20,7 @@ describe API::PipelineSchedules do it 'returns list of pipeline_schedules' do get api("/projects/#{project.id}/pipeline_schedules", developer) - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(response).to match_response_schema('pipeline_schedules') end @@ -67,7 +67,7 @@ describe API::PipelineSchedules do it 'does not return pipeline_schedules list' do get api("/projects/#{project.id}/pipeline_schedules", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -75,7 +75,7 @@ describe API::PipelineSchedules do it 'does not return pipeline_schedules list' do get api("/projects/#{project.id}/pipeline_schedules") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -91,14 +91,14 @@ describe API::PipelineSchedules do it 'returns pipeline_schedule details' do get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer) - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule') end it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do get api("/projects/#{project.id}/pipeline_schedules/-5", developer) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -106,7 +106,7 @@ describe API::PipelineSchedules do it 'does not return pipeline_schedules list' do get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -118,7 +118,7 @@ describe API::PipelineSchedules do it 'does not return pipeline_schedules list' do get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -126,7 +126,7 @@ describe API::PipelineSchedules do it 'does not return pipeline_schedules list' do get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -142,7 +142,7 @@ describe API::PipelineSchedules do params end.to change { project.pipeline_schedules.count }.by(1) - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('pipeline_schedule') expect(json_response['description']).to eq(params[:description]) expect(json_response['ref']).to eq(params[:ref]) @@ -156,7 +156,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule' do post api("/projects/#{project.id}/pipeline_schedules", developer) - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end @@ -165,7 +165,7 @@ describe API::PipelineSchedules do post api("/projects/#{project.id}/pipeline_schedules", developer), params.merge('cron' => 'invalid-cron') - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('cron') end end @@ -175,7 +175,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule' do post api("/projects/#{project.id}/pipeline_schedules", user), params - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -183,7 +183,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule' do post api("/projects/#{project.id}/pipeline_schedules"), params - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -198,7 +198,7 @@ describe API::PipelineSchedules do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer), cron: '1 2 3 4 *' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule') expect(json_response['cron']).to eq('1 2 3 4 *') end @@ -208,7 +208,7 @@ describe API::PipelineSchedules do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer), cron: 'invalid-cron' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('cron') end end @@ -218,7 +218,7 @@ describe API::PipelineSchedules do it 'does not update pipeline_schedule' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -226,7 +226,7 @@ describe API::PipelineSchedules do it 'does not update pipeline_schedule' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -240,7 +240,7 @@ describe API::PipelineSchedules do it 'updates owner' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer) - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('pipeline_schedule') end end @@ -249,7 +249,7 @@ describe API::PipelineSchedules do it 'does not update owner' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -257,7 +257,7 @@ describe API::PipelineSchedules do it 'does not update owner' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -279,13 +279,13 @@ describe API::PipelineSchedules do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master) end.to change { project.pipeline_schedules.count }.by(-1) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do delete api("/projects/#{project.id}/pipeline_schedules/-5", master) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end it_behaves_like '412 response' do @@ -299,7 +299,7 @@ describe API::PipelineSchedules do it 'does not delete pipeline_schedule' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer) - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) end end @@ -307,7 +307,7 @@ describe API::PipelineSchedules do it 'does not delete pipeline_schedule' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -327,7 +327,7 @@ describe API::PipelineSchedules do params end.to change { pipeline_schedule.variables.count }.by(1) - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('pipeline_schedule_variable') expect(json_response['key']).to eq(params[:key]) expect(json_response['value']).to eq(params[:value]) @@ -338,7 +338,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule_variable' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer) - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end @@ -347,7 +347,7 @@ describe API::PipelineSchedules do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer), params.merge('key' => '!?!?') - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to have_key('key') end end @@ -357,7 +357,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule_variable' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -365,7 +365,7 @@ describe API::PipelineSchedules do it 'does not create pipeline_schedule_variable' do post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -384,7 +384,7 @@ describe API::PipelineSchedules do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer), value: 'updated_value' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('pipeline_schedule_variable') expect(json_response['value']).to eq('updated_value') end @@ -394,7 +394,7 @@ describe API::PipelineSchedules do it 'does not update pipeline_schedule_variable' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", user) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -402,7 +402,7 @@ describe API::PipelineSchedules do it 'does not update pipeline_schedule_variable' do put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -428,14 +428,14 @@ describe API::PipelineSchedules do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", master) end.to change { Ci::PipelineScheduleVariable.count }.by(-1) - expect(response).to have_http_status(:accepted) + expect(response).to have_gitlab_http_status(:accepted) expect(response).to match_response_schema('pipeline_schedule_variable') end it 'responds with 404 Not Found if requesting non-existing pipeline_schedule_variable' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/____", master) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -445,7 +445,7 @@ describe API::PipelineSchedules do it 'does not delete pipeline_schedule_variable' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer) - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) end end @@ -453,7 +453,7 @@ describe API::PipelineSchedules do it 'does not delete pipeline_schedule_variable' do delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}") - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 258085e503f..e4dcc9252fa 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -19,7 +19,7 @@ describe API::Pipelines do it 'returns project pipelines' do get api("/projects/#{project.id}/pipelines", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['sha']).to match /\A\h{40}\z/ @@ -37,7 +37,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), scope: target - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty json_response.each { |r| expect(r['status']).to eq(target) } @@ -55,7 +55,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), scope: 'finished' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty json_response.each { |r| expect(r['status']).to be_in(%w[success failed canceled]) } @@ -70,7 +70,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), scope: 'branches' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty expect(json_response.last['id']).to eq(pipeline_branch.id) @@ -81,7 +81,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), scope: 'tags' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty expect(json_response.last['id']).to eq(pipeline_tag.id) @@ -93,7 +93,7 @@ describe API::Pipelines do it 'returns bad_request' do get api("/projects/#{project.id}/pipelines", user), scope: 'invalid-scope' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end @@ -108,7 +108,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), status: target - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty json_response.each { |r| expect(r['status']).to eq(target) } @@ -120,7 +120,7 @@ describe API::Pipelines do it 'returns bad_request' do get api("/projects/#{project.id}/pipelines", user), status: 'invalid-status' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end @@ -133,7 +133,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), ref: 'master' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty json_response.each { |r| expect(r['ref']).to eq('master') } @@ -144,7 +144,7 @@ describe API::Pipelines do it 'returns empty' do get api("/projects/#{project.id}/pipelines", user), ref: 'invalid-ref' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_empty end @@ -158,7 +158,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), name: user.name - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response.first['id']).to eq(pipeline.id) end @@ -168,7 +168,7 @@ describe API::Pipelines do it 'returns empty' do get api("/projects/#{project.id}/pipelines", user), name: 'invalid-name' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_empty end @@ -182,7 +182,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), username: user.username - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response.first['id']).to eq(pipeline.id) end @@ -192,7 +192,7 @@ describe API::Pipelines do it 'returns empty' do get api("/projects/#{project.id}/pipelines", user), username: 'invalid-username' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_empty end @@ -207,7 +207,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), yaml_errors: true - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response.first['id']).to eq(pipeline1.id) end @@ -217,7 +217,7 @@ describe API::Pipelines do it 'returns matched pipelines' do get api("/projects/#{project.id}/pipelines", user), yaml_errors: false - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response.first['id']).to eq(pipeline2.id) end @@ -227,7 +227,7 @@ describe API::Pipelines do it 'returns bad_request' do get api("/projects/#{project.id}/pipelines", user), yaml_errors: 'invalid-yaml_errors' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end end @@ -244,7 +244,7 @@ describe API::Pipelines do it 'sorts as user_id: :desc' do get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'desc' - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).not_to be_empty @@ -257,7 +257,7 @@ describe API::Pipelines do it 'returns bad_request' do get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'invalid_sort' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end end @@ -266,7 +266,7 @@ describe API::Pipelines do it 'returns bad_request' do get api("/projects/#{project.id}/pipelines", user), order_by: 'lock_version', sort: 'asc' - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end end @@ -277,7 +277,7 @@ describe API::Pipelines do it 'does not return project pipelines' do get api("/projects/#{project.id}/pipelines", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response).not_to be_an Array end @@ -296,7 +296,7 @@ describe API::Pipelines do post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch end.to change { Ci::Pipeline.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a Hash expect(json_response['sha']).to eq project.commit.id end @@ -304,7 +304,7 @@ describe API::Pipelines do it 'fails when using an invalid ref' do post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Reference not found' expect(json_response).not_to be_an Array end @@ -314,7 +314,7 @@ describe API::Pipelines do it 'fails to create pipeline' do post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' expect(json_response).not_to be_an Array end @@ -325,7 +325,7 @@ describe API::Pipelines do it 'does not create pipeline' do post api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response).not_to be_an Array end @@ -337,14 +337,14 @@ describe API::Pipelines do it 'returns project pipelines' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['sha']).to match /\A\h{40}\z/ end it 'returns 404 when it does not exist' do get api("/projects/#{project.id}/pipelines/123456", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Not found' expect(json_response['id']).to be nil end @@ -366,7 +366,7 @@ describe API::Pipelines do it 'should not return a project pipeline' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response['id']).to be nil end @@ -387,7 +387,7 @@ describe API::Pipelines do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) end.to change { pipeline.builds.count }.from(1).to(2) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(build.reload.retried?).to be true end end @@ -396,7 +396,7 @@ describe API::Pipelines do it 'should not return a project pipeline' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response['id']).to be nil end @@ -415,7 +415,7 @@ describe API::Pipelines do it 'retries failed builds' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('canceled') end end @@ -430,7 +430,7 @@ describe API::Pipelines do it 'rejects the action' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(pipeline.reload.status).to eq('pending') end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index ac3bab09c4c..f31344a6238 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -22,7 +22,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns project hooks" do get api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response).to include_pagination_headers expect(json_response.count).to eq(1) @@ -43,7 +43,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "does not access project hooks" do get api("/projects/#{project.id}/hooks", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -53,7 +53,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a project hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['url']).to eq(hook.url) expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(hook.push_events) @@ -69,20 +69,20 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context "unauthorized user" do it "does not access an existing hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it "returns a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -94,7 +94,7 @@ describe API::ProjectHooks, 'ProjectHooks' do job_events: true end.to change {project.hooks.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['url']).to eq('http://example.com') expect(json_response['issues_events']).to eq(true) expect(json_response['push_events']).to eq(true) @@ -115,7 +115,7 @@ describe API::ProjectHooks, 'ProjectHooks' do post api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token end.to change {project.hooks.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["url"]).to eq("http://example.com") expect(json_response).not_to include("token") @@ -127,12 +127,12 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 400 error if url not given" do post api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 422 error if url not valid" do post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -141,7 +141,7 @@ describe API::ProjectHooks, 'ProjectHooks' do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'http://example.org', push_events: false, job_events: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['url']).to eq('http://example.org') expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(false) @@ -159,7 +159,7 @@ describe API::ProjectHooks, 'ProjectHooks' do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["url"]).to eq("http://example.org") expect(json_response).not_to include("token") @@ -169,17 +169,17 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns 404 error if hook id not found" do put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 400 error if url is not given" do put api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 422 error if url is not valid" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -188,19 +188,19 @@ describe API::ProjectHooks, 'ProjectHooks' do expect do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change {project.hooks.count}.by(-1) end it "returns a 404 error when deleting non existent hook" do delete api("/projects/#{project.id}/hooks/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 404 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do @@ -209,7 +209,7 @@ describe API::ProjectHooks, 'ProjectHooks' do other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(WebHook.exists?(hook.id)).to be_truthy end diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index db34149eb73..e741ac4b7bd 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -12,7 +12,7 @@ describe API::ProjectSnippets do it 'exposes known attributes' do get api("/projects/#{project.id}/snippets/#{snippet.id}/user_agent_detail", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) @@ -21,7 +21,7 @@ describe API::ProjectSnippets do it "returns unautorized for non-admin users" do get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -36,7 +36,7 @@ describe API::ProjectSnippets do get api("/projects/#{project.id}/snippets", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(3) @@ -49,7 +49,7 @@ describe API::ProjectSnippets do get api("/projects/#{project.id}/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(0) @@ -63,7 +63,7 @@ describe API::ProjectSnippets do it 'returns snippet json' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(snippet.title) expect(json_response['description']).to eq(snippet.description) @@ -73,7 +73,7 @@ describe API::ProjectSnippets do it 'returns 404 for invalid snippet id' do get api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end end @@ -92,7 +92,7 @@ describe API::ProjectSnippets do it 'creates a new snippet' do post api("/projects/#{project.id}/snippets/", admin), params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) snippet = ProjectSnippet.find(json_response['id']) expect(snippet.content).to eq(params[:code]) expect(snippet.description).to eq(params[:description]) @@ -106,7 +106,7 @@ describe API::ProjectSnippets do post api("/projects/#{project.id}/snippets/", admin), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -132,7 +132,7 @@ describe API::ProjectSnippets do expect { create_snippet(project, visibility: 'public') } .not_to change { Snippet.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -154,7 +154,7 @@ describe API::ProjectSnippets do put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content, description: new_description - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) snippet.reload expect(snippet.content).to eq(new_content) expect(snippet.description).to eq(new_description) @@ -163,14 +163,14 @@ describe API::ProjectSnippets do it 'returns 404 for invalid snippet id' do put api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it 'returns 400 for missing parameters' do put api("/projects/#{project.id}/snippets/1234", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -212,7 +212,7 @@ describe API::ProjectSnippets do expect { update_snippet(title: 'Foo', visibility: 'public') } .not_to change { snippet.reload.title } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -230,13 +230,13 @@ describe API::ProjectSnippets do it 'deletes snippet' do delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end it 'returns 404 for invalid snippet id' do delete api("/projects/#{snippet.project.id}/snippets/1234", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end @@ -251,7 +251,7 @@ describe API::ProjectSnippets do it 'returns raw text' do get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end @@ -259,7 +259,7 @@ describe API::ProjectSnippets do it 'returns 404 for invalid snippet id' do get api("/projects/#{snippet.project.id}/snippets/1234/raw", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 508df990952..e095ba2af5d 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -45,7 +45,7 @@ describe API::Projects do it 'returns an array of projects' do get api('/projects', current_user), filter - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) @@ -64,9 +64,12 @@ describe API::Projects do create(:project, :public) end + # TODO: We're currently querying to detect if a project is a fork + # in 2 ways. Lower this back to 8 when `ForkedProjectLink` relation is + # removed expect do get api('/projects', current_user) - end.not_to exceed_query_limit(control).with_threshold(8) + end.not_to exceed_query_limit(control).with_threshold(9) end end @@ -144,7 +147,7 @@ describe API::Projects do it "does not include statistics by default" do get api('/projects', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first).not_to include('statistics') @@ -153,7 +156,7 @@ describe API::Projects do it "includes statistics if requested" do get api('/projects', user), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first).to include 'statistics' @@ -193,11 +196,12 @@ describe API::Projects do path path_with_namespace star_count forks_count created_at last_activity_at + avatar_url ) get api('/projects?simple=true', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first.keys).to match_array expected_keys @@ -224,7 +228,7 @@ describe API::Projects do it 'filters based on private visibility param' do get api('/projects', user), { visibility: 'private' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id) @@ -235,7 +239,7 @@ describe API::Projects do get api('/projects', user), { visibility: 'internal' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id) @@ -244,7 +248,7 @@ describe API::Projects do it 'filters based on public visibility param' do get api('/projects', user), { visibility: 'public' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id) @@ -255,7 +259,7 @@ describe API::Projects do it 'returns the correct order when sorted by id' do get api('/projects', user), { order_by: 'id', sort: 'desc' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project3.id) @@ -266,7 +270,7 @@ describe API::Projects do it 'returns an array of projects the user owns' do get api('/projects', user4), owned: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['name']).to eq(project4.name) @@ -285,7 +289,7 @@ describe API::Projects do it 'returns the starred projects viewable by the user' do get api('/projects', user3), starred: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) @@ -307,7 +311,7 @@ describe API::Projects do it 'returns only projects that satisfy all query parameters' do get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -326,7 +330,7 @@ describe API::Projects do it 'returns only projects that satisfy all query parameters' do get api('/projects', user), { visibility: 'public', membership: true, starred: true, search: 'gitlab' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) @@ -359,14 +363,14 @@ describe API::Projects do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) expect { post api('/projects', user2), name: 'foo' } .to change {Project.count}.by(0) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'creates new project without path but with name and returns 201' do expect { post api('/projects', user), name: 'Foo Project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -377,7 +381,7 @@ describe API::Projects do it 'creates new project without name but with path and returns 201' do expect { post api('/projects', user), path: 'foo_project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -388,7 +392,7 @@ describe API::Projects do it 'creates new project with name and path and returns 201' do expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -399,12 +403,12 @@ describe API::Projects do it 'creates last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) post api('/projects', user2), name: 'foo' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'does not create new project without name or path and returns 400' do expect { post api('/projects', user) }.not_to change { Project.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "assigns attributes to project" do @@ -423,7 +427,7 @@ describe API::Projects do post api('/projects', user), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) @@ -539,7 +543,7 @@ describe API::Projects do post api('/projects', user), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when a visibility level is restricted' do @@ -552,7 +556,7 @@ describe API::Projects do it 'does not allow a non-admin to use a restricted visibility level' do post api('/projects', user), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['visibility_level'].first).to( match('restricted by your GitLab administrator') ) @@ -572,14 +576,14 @@ describe API::Projects do it 'returns error when user not found' do get api('/users/9999/projects/') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns projects filtered by user' do get api("/users/#{user4.id}/projects/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) @@ -593,7 +597,7 @@ describe API::Projects do it 'creates new project without path but with name and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.last @@ -604,7 +608,7 @@ describe API::Projects do it 'creates new project with name and path and returns 201' do expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.last @@ -616,7 +620,7 @@ describe API::Projects do expect { post api("/projects/user/#{user.id}", admin) } .not_to change { Project.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('name is missing') end @@ -630,7 +634,7 @@ describe API::Projects do post api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -642,7 +646,7 @@ describe API::Projects do post api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['visibility']).to eq('public') end @@ -651,7 +655,7 @@ describe API::Projects do post api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['visibility']).to eq('internal') end @@ -716,7 +720,7 @@ describe API::Projects do it "uploads the file and returns its info" do post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['alt']).to eq("dk") expect(json_response['url']).to start_with("/uploads/") expect(json_response['url']).to end_with("/dk.png") @@ -730,7 +734,7 @@ describe API::Projects do get api("/projects/#{public_project.id}") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) expect(json_response['default_branch']).to eq(public_project.default_branch) @@ -750,7 +754,7 @@ describe API::Projects do get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(project.id) expect(json_response['description']).to eq(project.description) expect(json_response['default_branch']).to eq(project.default_branch) @@ -794,20 +798,20 @@ describe API::Projects do it 'returns a project by path name' do get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(project.name) end it 'returns a 404 error if not found' do get api('/projects/42', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'returns a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}", other_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'handles users with dots' do @@ -815,14 +819,14 @@ describe API::Projects do project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{CGI.escape(project.full_path)}", dot_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(project.name) end it 'exposes namespace fields' do get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['namespace']).to eq({ 'id' => user.namespace.id, 'name' => user.namespace.name, @@ -836,28 +840,28 @@ describe API::Projects do it "does not include statistics by default" do get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).not_to include 'statistics' end it "includes statistics if requested" do get api("/projects/#{project.id}", user), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to include 'statistics' end it "includes import_error if user can admin project" do get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to include("import_error") end it "does not include import_error if user cannot admin project" do get api("/projects/#{project.id}", user3) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).not_to include("import_error") end @@ -902,7 +906,7 @@ describe API::Projects do it 'contains permission information' do get api("/projects", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.first['permissions']['project_access']['access_level']) .to eq(Gitlab::Access::MASTER) expect(json_response.first['permissions']['group_access']).to be_nil @@ -914,7 +918,7 @@ describe API::Projects do project.team << [user, :master] get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['permissions']['project_access']['access_level']) .to eq(Gitlab::Access::MASTER) expect(json_response['permissions']['group_access']).to be_nil @@ -931,7 +935,7 @@ describe API::Projects do it 'sets the owner and return 200' do get api("/projects/#{project2.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['permissions']['project_access']).to be_nil expect(json_response['permissions']['group_access']['access_level']) .to eq(Gitlab::Access::OWNER) @@ -948,7 +952,7 @@ describe API::Projects do user = project.namespace.owner - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -977,7 +981,7 @@ describe API::Projects do it 'returns a 404 error if not found' do get api('/projects/42/users', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end @@ -986,7 +990,7 @@ describe API::Projects do get api("/projects/#{project.id}/users", other_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -999,7 +1003,7 @@ describe API::Projects do it 'returns an array of project snippets' do get api("/projects/#{project.id}/snippets", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['title']).to eq(snippet.title) @@ -1009,13 +1013,13 @@ describe API::Projects do describe 'GET /projects/:id/snippets/:snippet_id' do it 'returns a project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(snippet.title) end it 'returns a 404 error if snippet id not found' do get api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1023,7 +1027,7 @@ describe API::Projects do it 'creates a new project snippet' do post api("/projects/#{project.id}/snippets", user), title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('api test') end @@ -1037,7 +1041,7 @@ describe API::Projects do it 'updates an existing project snippet' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('example') expect(snippet.reload.content).to eq('updated code') end @@ -1045,7 +1049,7 @@ describe API::Projects do it 'updates an existing project snippet with new title' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other api test' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('other api test') end end @@ -1059,13 +1063,13 @@ describe API::Projects do expect do delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { Snippet.count }.by(-1) end it 'returns 404 when deleting unknown snippet id' do delete api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -1076,12 +1080,12 @@ describe API::Projects do describe 'GET /projects/:id/snippets/:snippet_id/raw' do it 'gets a raw project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns a 404 error if raw project snippet not found' do get api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1094,13 +1098,13 @@ describe API::Projects do it "is not available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'allows project to be forked from an existing project' do expect(project_fork_target.forked?).not_to be_truthy post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked_project_link).not_to be_nil @@ -1117,7 +1121,7 @@ describe API::Projects do it 'fails if forked_from project which does not exist' do post api("/projects/#{project_fork_target.id}/fork/9999", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'fails with 409 if already forked' do @@ -1125,7 +1129,7 @@ describe API::Projects do project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked?).to be_truthy @@ -1135,7 +1139,7 @@ describe API::Projects do describe 'DELETE /projects/:id/fork' do it "is not visible to users outside group" do delete api("/projects/#{project_fork_target.id}/fork", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'when users belong to project group' do @@ -1157,7 +1161,7 @@ describe API::Projects do it 'makes forked project unforked' do delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) project_fork_target.reload expect(project_fork_target.forked_from_project).to be_nil expect(project_fork_target.forked?).not_to be_truthy @@ -1170,13 +1174,13 @@ describe API::Projects do it 'is forbidden to non-owner users' do delete api("/projects/#{project_fork_target.id}/fork", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -1206,7 +1210,7 @@ describe API::Projects do it 'returns the forks' do get api("/projects/#{project_fork_source.id}/forks", member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(1) expect(json_response[0]['name']).to eq(private_fork.name) @@ -1217,7 +1221,7 @@ describe API::Projects do it 'returns an empty array' do get api("/projects/#{project_fork_source.id}/forks", non_member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(0) end @@ -1228,7 +1232,7 @@ describe API::Projects do it 'returns an empty array' do get api("/projects/#{project_fork_source.id}/forks") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.length).to eq(0) end @@ -1246,7 +1250,7 @@ describe API::Projects do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -1254,18 +1258,18 @@ describe API::Projects do it "returns a 400 error when group id is not given" do post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when access level is not given" do post api("/projects/#{project.id}/share", user), group_id: group.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -1273,19 +1277,19 @@ describe API::Projects do post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when group does not exist' do post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 400 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'group_access does not have a valid value' end end @@ -1301,7 +1305,7 @@ describe API::Projects do it 'returns 204 when deleting a group share' do delete api("/projects/#{project.id}/share/#{group.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(project.project_group_links).to be_empty end @@ -1313,19 +1317,19 @@ describe API::Projects do it 'returns a 400 when group id is not an integer' do delete api("/projects/#{project.id}/share/foo", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 404 error when group link does not exist' do delete api("/projects/#{project.id}/share/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when project does not exist' do delete api("/projects/123/share/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1346,7 +1350,7 @@ describe API::Projects do put api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to match('at least one parameter must be provided') end @@ -1356,7 +1360,7 @@ describe API::Projects do put api("/projects/#{project.id}"), project_param - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -1366,7 +1370,7 @@ describe API::Projects do put api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1378,7 +1382,7 @@ describe API::Projects do put api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1391,7 +1395,7 @@ describe API::Projects do put api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1405,7 +1409,7 @@ describe API::Projects do put api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['name']).to eq(['has already been taken']) end @@ -1414,7 +1418,7 @@ describe API::Projects do put api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['request_access_enabled']).to eq(false) end @@ -1423,7 +1427,7 @@ describe API::Projects do put api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1435,7 +1439,7 @@ describe API::Projects do put api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) @@ -1447,7 +1451,7 @@ describe API::Projects do it 'updates path' do project_param = { path: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1461,7 +1465,7 @@ describe API::Projects do description: 'new description' } put api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1470,20 +1474,20 @@ describe API::Projects do it 'does not update path to existing path' do project_param = { path: project.path } put api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'does not update name' do project_param = { name: 'bar' } put api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not update visibility_level' do project_param = { visibility: 'public' } put api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1497,7 +1501,7 @@ describe API::Projects do description: 'new description', request_access_enabled: true } put api("/projects/#{project.id}", user3), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1507,7 +1511,7 @@ describe API::Projects do it 'archives the project' do post api("/projects/#{project.id}/archive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -1520,7 +1524,7 @@ describe API::Projects do it 'remains archived' do post api("/projects/#{project.id}/archive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -1533,7 +1537,7 @@ describe API::Projects do it 'rejects the action' do post api("/projects/#{project.id}/archive", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1543,7 +1547,7 @@ describe API::Projects do it 'remains unarchived' do post api("/projects/#{project.id}/unarchive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1556,7 +1560,7 @@ describe API::Projects do it 'unarchives the project' do post api("/projects/#{project.id}/unarchive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1569,7 +1573,7 @@ describe API::Projects do it 'rejects the action' do post api("/projects/#{project.id}/unarchive", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1579,7 +1583,7 @@ describe API::Projects do it 'stars the project' do expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['star_count']).to eq(1) end end @@ -1593,7 +1597,7 @@ describe API::Projects do it 'does not modify the star count' do expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end end @@ -1608,7 +1612,7 @@ describe API::Projects do it 'unstars the project' do expect { post api("/projects/#{project.id}/unstar", user) }.to change { project.reload.star_count }.by(-1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['star_count']).to eq(0) end end @@ -1617,7 +1621,7 @@ describe API::Projects do it 'does not modify the star count' do expect { post api("/projects/#{project.id}/unstar", user) }.not_to change { project.reload.star_count } - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end end @@ -1627,7 +1631,7 @@ describe API::Projects do it 'removes project' do delete api("/projects/#{project.id}", user) - expect(response).to have_http_status(202) + expect(response).to have_gitlab_http_status(202) expect(json_response['message']).to eql('202 Accepted') end @@ -1640,17 +1644,17 @@ describe API::Projects do user3 = create(:user) project.team << [user3, :developer] delete api("/projects/#{project.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not remove a non existing project' do delete api('/projects/1328', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not remove a project not attached to user' do delete api("/projects/#{project.id}", user2) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1658,13 +1662,13 @@ describe API::Projects do it 'removes any existing project' do delete api("/projects/#{project.id}", admin) - expect(response).to have_http_status(202) + expect(response).to have_gitlab_http_status(202) expect(json_response['message']).to eql('202 Accepted') end it 'does not remove a non existing project' do delete api('/projects/1328', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -1693,7 +1697,7 @@ describe API::Projects do it 'forks if user has sufficient access to project' do post api("/projects/#{project.id}/fork", user2) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(user2.id) @@ -1706,7 +1710,7 @@ describe API::Projects do it 'forks if user is admin' do post api("/projects/#{project.id}/fork", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(project.name) expect(json_response['path']).to eq(project.path) expect(json_response['owner']['id']).to eq(admin.id) @@ -1720,14 +1724,14 @@ describe API::Projects do new_user = create(:user) post api("/projects/#{project.id}/fork", new_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'fails if forked project exists in the user namespace' do post api("/projects/#{project.id}/fork", user) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken']) end @@ -1735,61 +1739,61 @@ describe API::Projects do it 'fails if project to fork from does not exist' do post api('/projects/424242/fork', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'forks with explicit own user namespace id' do post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'forks with explicit own user name as namespace' do post api("/projects/#{project.id}/fork", user2), namespace: user2.username - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'forks to another user when admin' do post api("/projects/#{project.id}/fork", admin), namespace: user2.username - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['owner']['id']).to eq(user2.id) end it 'fails if trying to fork to another user when not admin' do post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'fails if trying to fork to non-existent namespace' do post api("/projects/#{project.id}/fork", user2), namespace: 42424242 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Target Namespace Not Found') end it 'forks to owned group' do post api("/projects/#{project.id}/fork", user2), namespace: group2.name - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group2.name) end it 'fails to fork to not owned group' do post api("/projects/#{project.id}/fork", user2), namespace: group.name - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'forks to not owned group when admin' do post api("/projects/#{project.id}/fork", admin), namespace: group.name - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group.name) end end @@ -1798,7 +1802,7 @@ describe API::Projects do it 'returns authentication error' do post api("/projects/#{project.id}/fork") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') end end @@ -1817,7 +1821,7 @@ describe API::Projects do post api("/projects/#{project.id}/housekeeping", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when housekeeping lease is taken' do @@ -1826,7 +1830,7 @@ describe API::Projects do post api("/projects/#{project.id}/housekeeping", user) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to match(/Somebody already triggered housekeeping for this project/) end end @@ -1840,7 +1844,7 @@ describe API::Projects do it 'returns forbidden error' do post api("/projects/#{project.id}/housekeeping", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1848,7 +1852,7 @@ describe API::Projects do it 'returns authentication error' do post api("/projects/#{project.id}/housekeeping") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 1a0695615e3..9f2ff3b5af6 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -17,7 +17,7 @@ describe API::Repositories do it 'returns the repository tree' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -106,7 +106,7 @@ describe API::Repositories do it 'returns blob attributes as json' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['size']).to eq(111) expect(json_response['encoding']).to eq("base64") expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n") @@ -165,7 +165,7 @@ describe API::Repositories do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when sha does not exist' do @@ -218,7 +218,7 @@ describe API::Repositories do it 'returns the repository archive' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data @@ -230,7 +230,7 @@ describe API::Repositories do it 'returns the repository archive archive.zip' do get api("/projects/#{project.id}/repository/archive.zip", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data @@ -242,7 +242,7 @@ describe API::Repositories do it 'returns the repository archive archive.tar.bz2' do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data @@ -293,7 +293,7 @@ describe API::Repositories do it "compares branches" do get api(route, current_user), from: 'master', to: 'feature' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end @@ -301,7 +301,7 @@ describe API::Repositories do it "compares tags" do get api(route, current_user), from: 'v1.0.0', to: 'v1.1.0' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end @@ -309,7 +309,7 @@ describe API::Repositories do it "compares commits" do get api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_falsey @@ -318,7 +318,7 @@ describe API::Repositories do it "compares commits in reverse order" do get api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end @@ -326,7 +326,7 @@ describe API::Repositories do it "compares same refs" do get api(route, current_user), from: 'master', to: 'master' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_truthy @@ -367,7 +367,7 @@ describe API::Repositories do it 'returns valid data' do get api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 12720355a6d..47f4ccd4887 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -16,7 +16,7 @@ describe API::Runner do it 'returns 400 error' do post api('/runners') - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status 400 end end @@ -24,7 +24,7 @@ describe API::Runner do it 'returns 403 error' do post api('/runners'), token: 'invalid' - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status 403 end end @@ -34,7 +34,7 @@ describe API::Runner do runner = Ci::Runner.first - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(json_response['id']).to eq(runner.id) expect(json_response['token']).to eq(runner.token) expect(runner.run_untagged).to be true @@ -47,7 +47,7 @@ describe API::Runner do it 'creates runner' do post api('/runners'), token: project.runners_token - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(project.runners.size).to eq(1) expect(Ci::Runner.first.token).not_to eq(registration_token) expect(Ci::Runner.first.token).not_to eq(project.runners_token) @@ -60,7 +60,7 @@ describe API::Runner do post api('/runners'), token: registration_token, description: 'server.hostname' - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.description).to eq('server.hostname') end end @@ -70,7 +70,7 @@ describe API::Runner do post api('/runners'), token: registration_token, tag_list: 'tag1, tag2' - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2)) end end @@ -82,7 +82,7 @@ describe API::Runner do run_untagged: false, tag_list: ['tag'] - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.run_untagged).to be false expect(Ci::Runner.first.tag_list.sort).to eq(['tag']) end @@ -93,7 +93,7 @@ describe API::Runner do post api('/runners'), token: registration_token, run_untagged: false - expect(response).to have_http_status 404 + expect(response).to have_gitlab_http_status 404 end end end @@ -103,7 +103,7 @@ describe API::Runner do post api('/runners'), token: registration_token, locked: true - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.locked).to be true end end @@ -116,7 +116,7 @@ describe API::Runner do post api('/runners'), token: registration_token, info: { param => value } - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value) end end @@ -128,7 +128,7 @@ describe API::Runner do it 'returns 400 error' do delete api('/runners') - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status 400 end end @@ -136,7 +136,7 @@ describe API::Runner do it 'returns 403 error' do delete api('/runners'), token: 'invalid' - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status 403 end end @@ -146,7 +146,7 @@ describe API::Runner do it 'deletes Runner' do delete api('/runners'), token: runner.token - expect(response).to have_http_status 204 + expect(response).to have_gitlab_http_status 204 expect(Ci::Runner.count).to eq(0) end @@ -164,7 +164,7 @@ describe API::Runner do it 'returns 400 error' do post api('/runners/verify') - expect(response).to have_http_status :bad_request + expect(response).to have_gitlab_http_status :bad_request end end @@ -172,7 +172,7 @@ describe API::Runner do it 'returns 403 error' do post api('/runners/verify'), token: 'invalid-token' - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status 403 end end @@ -180,7 +180,7 @@ describe API::Runner do it 'verifies Runner credentials' do post api('/runners/verify'), token: runner.token - expect(response).to have_http_status 200 + expect(response).to have_gitlab_http_status 200 end end end @@ -216,7 +216,7 @@ describe API::Runner do context 'when runner sends version in User-Agent' do context 'for stable version' do it 'gives 204 and set X-GitLab-Last-Update' do - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(response.header).to have_key('X-GitLab-Last-Update') end end @@ -225,7 +225,7 @@ describe API::Runner do let(:last_update) { runner.ensure_runner_queue_value } it 'gives 204 and set the same X-GitLab-Last-Update' do - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(response.header['X-GitLab-Last-Update']).to eq(last_update) end end @@ -235,7 +235,7 @@ describe API::Runner do let(:new_update) { runner.tick_runner_queue } it 'gives 204 and set a new X-GitLab-Last-Update' do - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(response.header['X-GitLab-Last-Update']).to eq(new_update) end end @@ -243,19 +243,19 @@ describe API::Runner do context 'when beta version is sent' do let(:user_agent) { 'gitlab-runner 9.0.0~beta.167.g2b2bacc (master; go1.7.4; linux/amd64)' } - it { expect(response).to have_http_status(204) } + it { expect(response).to have_gitlab_http_status(204) } end context 'when pre-9-0 version is sent' do let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0 (1-6-stable; go1.6.3; linux/amd64)' } - it { expect(response).to have_http_status(204) } + it { expect(response).to have_gitlab_http_status(204) } end context 'when pre-9-0 beta version is sent' do let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (master; go1.6.3; linux/amd64)' } - it { expect(response).to have_http_status(204) } + it { expect(response).to have_gitlab_http_status(204) } end end end @@ -264,7 +264,7 @@ describe API::Runner do it 'returns 400 error' do post api('/jobs/request') - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status 400 end end @@ -272,7 +272,7 @@ describe API::Runner do it 'returns 403 error' do post api('/jobs/request'), token: 'invalid' - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status 403 end end @@ -283,7 +283,7 @@ describe API::Runner do it 'returns 204 error' do request_job - expect(response).to have_http_status 204 + expect(response).to have_gitlab_http_status 204 end end @@ -360,10 +360,12 @@ describe API::Runner do 'policy' => 'pull-push' }] end + let(:expected_features) { { 'trace_sections' => true } } + it 'picks a job' do request_job info: { platform: :darwin } - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(response.headers).not_to have_key('X-GitLab-Last-Update') expect(runner.reload.platform).to eq('darwin') expect(json_response['id']).to eq(job.id) @@ -379,15 +381,16 @@ describe API::Runner do expect(json_response['artifacts']).to eq(expected_artifacts) expect(json_response['cache']).to eq(expected_cache) expect(json_response['variables']).to include(*expected_variables) + expect(json_response['features']).to eq(expected_features) end context 'when job is made for tag' do - let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } it 'sets branch as ref_type' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['git_info']['ref_type']).to eq('tag') end end @@ -396,7 +399,7 @@ describe API::Runner do it 'sets tag as ref_type' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['git_info']['ref_type']).to eq('branch') end end @@ -412,7 +415,7 @@ describe API::Runner do it "updates provided Runner's parameter" do request_job info: { param => value } - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(runner.reload.read_attribute(param.to_sym)).to eq(value) end end @@ -427,14 +430,14 @@ describe API::Runner do it 'returns a conflict' do request_job - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(response.headers).not_to have_key('X-GitLab-Last-Update') end end context 'when project and pipeline have multiple jobs' do - let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build_tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } before do @@ -445,7 +448,7 @@ describe API::Runner do it 'returns dependent jobs' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(test_job.id) expect(json_response['dependencies'].count).to eq(2) expect(json_response['dependencies']).to include( @@ -455,7 +458,7 @@ describe API::Runner do end context 'when pipeline have jobs with artifacts' do - let!(:job) { create(:ci_build_tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } before do @@ -465,7 +468,7 @@ describe API::Runner do it 'returns dependent jobs' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(test_job.id) expect(json_response['dependencies'].count).to eq(1) expect(json_response['dependencies']).to include( @@ -475,8 +478,8 @@ describe API::Runner do end context 'when explicit dependencies are defined' do - let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build_tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } let!(:test_job) do create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy', stage: 'deploy', stage_idx: 1, @@ -491,7 +494,7 @@ describe API::Runner do it 'returns dependent jobs' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(test_job.id) expect(json_response['dependencies'].count).to eq(1) expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token) @@ -499,8 +502,8 @@ describe API::Runner do end context 'when dependencies is an empty array' do - let!(:job) { create(:ci_build_tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let!(:job2) { create(:ci_build_tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } + let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) } let!(:empty_dependencies_job) do create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job', stage: 'deploy', stage_idx: 1, @@ -515,7 +518,7 @@ describe API::Runner do it 'returns an empty array' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(empty_dependencies_job.id) expect(json_response['dependencies'].count).to eq(0) end @@ -534,7 +537,7 @@ describe API::Runner do it 'picks job' do request_job - expect(response).to have_http_status 201 + expect(response).to have_gitlab_http_status 201 end end @@ -568,7 +571,7 @@ describe API::Runner do it 'returns variables for triggers' do request_job - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['variables']).to include(*expected_variables) end end @@ -680,7 +683,7 @@ describe API::Runner do it 'updates a running build' do update_job(trace: 'BUILD TRACE UPDATED') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(job.reload.trace.raw).to eq 'BUILD TRACE UPDATED' end end @@ -699,7 +702,7 @@ describe API::Runner do it 'responds with forbidden' do update_job - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -868,7 +871,7 @@ describe API::Runner do it 'authorizes posting artifacts to running job' do authorize_artifacts_with_token_in_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response['TempPath']).not_to be_nil end @@ -878,7 +881,7 @@ describe API::Runner do authorize_artifacts_with_token_in_params(filesize: 100) - expect(response).to have_http_status(413) + expect(response).to have_gitlab_http_status(413) end end @@ -886,7 +889,7 @@ describe API::Runner do it 'authorizes posting artifacts to running job' do authorize_artifacts_with_token_in_headers - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response['TempPath']).not_to be_nil end @@ -896,7 +899,7 @@ describe API::Runner do authorize_artifacts_with_token_in_headers(filesize: 100) - expect(response).to have_http_status(413) + expect(response).to have_gitlab_http_status(413) end end @@ -904,7 +907,7 @@ describe API::Runner do it 'fails to authorize artifacts posting' do authorize_artifacts(token: job.project.runners_token) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -913,14 +916,14 @@ describe API::Runner do authorize_artifacts - expect(response).to have_http_status(500) + expect(response).to have_gitlab_http_status(500) end context 'authorization token is invalid' do it 'responds with forbidden' do authorize_artifacts(token: 'invalid', filesize: 100 ) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -955,14 +958,14 @@ describe API::Runner do it 'responds with forbidden' do upload_artifacts(file_upload, headers_with_token) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'when job is running' do shared_examples 'successful artifacts upload' do it 'updates successfully' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -995,7 +998,7 @@ describe API::Runner do it 'responds with forbidden' do upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token)) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1006,7 +1009,7 @@ describe API::Runner do upload_artifacts(file_upload, headers_with_token) - expect(response).to have_http_status(413) + expect(response).to have_gitlab_http_status(413) end end @@ -1014,7 +1017,7 @@ describe API::Runner do it 'fails to post artifacts without file' do post api("/jobs/#{job.id}/artifacts"), {}, headers_with_token - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -1022,7 +1025,7 @@ describe API::Runner do it 'fails to post artifacts without GitLab-Workhorse' do post api("/jobs/#{job.id}/artifacts"), { token: job.token }, {} - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1044,7 +1047,7 @@ describe API::Runner do let(:expire_in) { '7 days' } it 'updates when specified' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(7.days.from_now) end end @@ -1053,7 +1056,7 @@ describe API::Runner do let(:expire_in) { nil } it 'ignores if not specified' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(job.reload.artifacts_expire_at).to be_nil end @@ -1062,7 +1065,7 @@ describe API::Runner do let(:default_artifacts_expire_in) { '5 days' } it 'sets to application default' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(job.reload.artifacts_expire_at).to be_within(5.minutes).of(5.days.from_now) end end @@ -1071,7 +1074,7 @@ describe API::Runner do let(:default_artifacts_expire_in) { '0' } it 'does not set expire_in' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(job.reload.artifacts_expire_at).to be_nil end end @@ -1100,7 +1103,7 @@ describe API::Runner do end it 'stores artifacts and artifacts metadata' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) expect(stored_artifacts_size).to eq(71759) @@ -1113,7 +1116,7 @@ describe API::Runner do end it 'is expected to respond with bad request' do - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'does not store metadata' do @@ -1138,7 +1141,7 @@ describe API::Runner do it' "fails to post artifacts for outside of tmp path"' do upload_artifacts(file_upload, headers_with_token) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -1168,7 +1171,7 @@ describe API::Runner do context 'when using job token' do it 'download artifacts' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers).to include download_headers end end @@ -1177,14 +1180,14 @@ describe API::Runner do let(:token) { job.project.runners_token } it 'responds with forbidden' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end context 'when job does not has artifacts' do it 'responds with not found' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 67907579225..fe38a7b3251 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -37,7 +37,7 @@ describe API::Runners do get api('/runners', user) shared = json_response.any? { |r| r['is_shared'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(shared).to be_falsey @@ -47,7 +47,7 @@ describe API::Runners do get api('/runners?scope=active', user) shared = json_response.any? { |r| r['is_shared'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(shared).to be_falsey @@ -55,7 +55,7 @@ describe API::Runners do it 'avoids filtering if scope is invalid' do get api('/runners?scope=unknown', user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -63,7 +63,7 @@ describe API::Runners do it 'does not return runners' do get api('/runners') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -75,7 +75,7 @@ describe API::Runners do get api('/runners/all', admin) shared = json_response.any? { |r| r['is_shared'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(shared).to be_truthy @@ -86,7 +86,7 @@ describe API::Runners do it 'does not return runners list' do get api('/runners/all', user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -94,7 +94,7 @@ describe API::Runners do get api('/runners/all?scope=specific', admin) shared = json_response.any? { |r| r['is_shared'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(shared).to be_falsey @@ -102,7 +102,7 @@ describe API::Runners do it 'avoids filtering if scope is invalid' do get api('/runners?scope=unknown', admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -110,7 +110,7 @@ describe API::Runners do it 'does not return runners' do get api('/runners') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -121,7 +121,7 @@ describe API::Runners do it "returns runner's details" do get api("/runners/#{shared_runner.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -130,7 +130,7 @@ describe API::Runners do it "returns runner's details" do get api("/runners/#{specific_runner.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -138,7 +138,7 @@ describe API::Runners do it 'returns 404 if runner does not exists' do get api('/runners/9999', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -147,7 +147,7 @@ describe API::Runners do it "returns runner's details" do get api("/runners/#{specific_runner.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(specific_runner.description) end end @@ -156,7 +156,7 @@ describe API::Runners do it "returns runner's details" do get api("/runners/#{shared_runner.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(shared_runner.description) end end @@ -166,7 +166,7 @@ describe API::Runners do it "does not return runner's details" do get api("/runners/#{specific_runner.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -174,7 +174,7 @@ describe API::Runners do it "does not return runner's details" do get api("/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -195,7 +195,7 @@ describe API::Runners do access_level: 'ref_protected') shared_runner.reload - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(shared_runner.description).to eq("#{description}_updated") expect(shared_runner.active).to eq(!active) expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') @@ -215,7 +215,7 @@ describe API::Runners do update_runner(specific_runner.id, admin, description: 'test') specific_runner.reload - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) expect(specific_runner.ensure_runner_queue_value) @@ -226,7 +226,7 @@ describe API::Runners do it 'returns 404 if runner does not exists' do update_runner(9999, admin, description: 'test') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end def update_runner(id, user, args) @@ -239,7 +239,7 @@ describe API::Runners do it 'does not update runner' do put api("/runners/#{shared_runner.id}", user), description: 'test' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -247,7 +247,7 @@ describe API::Runners do it 'does not update runner without access to it' do put api("/runners/#{specific_runner.id}", user2), description: 'test' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'updates runner with access to it' do @@ -255,7 +255,7 @@ describe API::Runners do put api("/runners/#{specific_runner.id}", admin), description: 'test' specific_runner.reload - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(specific_runner.description).to eq('test') expect(specific_runner.description).not_to eq(description) end @@ -266,7 +266,7 @@ describe API::Runners do it 'does not delete runner' do put api("/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -278,7 +278,7 @@ describe API::Runners do expect do delete api("/runners/#{shared_runner.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { Ci::Runner.shared.count }.by(-1) end @@ -292,7 +292,7 @@ describe API::Runners do expect do delete api("/runners/#{unused_specific_runner.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { Ci::Runner.specific.count }.by(-1) end @@ -300,7 +300,7 @@ describe API::Runners do expect do delete api("/runners/#{specific_runner.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { Ci::Runner.specific.count }.by(-1) end end @@ -308,7 +308,7 @@ describe API::Runners do it 'returns 404 if runner does not exists' do delete api('/runners/9999', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -316,26 +316,26 @@ describe API::Runners do context 'when runner is shared' do it 'does not delete runner' do delete api("/runners/#{shared_runner.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'when runner is not shared' do it 'does not delete runner without access to it' do delete api("/runners/#{specific_runner.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not delete runner with more than one associated project' do delete api("/runners/#{two_projects_runner.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'deletes runner for one owned project' do expect do delete api("/runners/#{specific_runner.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { Ci::Runner.specific.count }.by(-1) end @@ -349,7 +349,7 @@ describe API::Runners do it 'does not delete runner' do delete api("/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -360,7 +360,7 @@ describe API::Runners do get api("/projects/#{project.id}/runners", user) shared = json_response.any? { |r| r['is_shared'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(shared).to be_truthy @@ -371,7 +371,7 @@ describe API::Runners do it "does not return project's runners" do get api("/projects/#{project.id}/runners", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -379,7 +379,7 @@ describe API::Runners do it "does not return project's runners" do get api("/projects/#{project.id}/runners") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -396,14 +396,14 @@ describe API::Runners do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change { project.runners.count }.by(+1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'avoids changes when enabling already enabled runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id end.to change { project.runners.count }.by(0) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) end it 'does not enable locked runner' do @@ -413,13 +413,13 @@ describe API::Runners do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change { project.runners.count }.by(0) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not enable shared runner' do post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'user is admin' do @@ -427,7 +427,7 @@ describe API::Runners do expect do post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id end.to change { project.runners.count }.by(+1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -435,14 +435,14 @@ describe API::Runners do it 'does not enable runner without access to' do post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'raises an error when no runner_id param is provided' do post api("/projects/#{project.id}/runners", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -450,7 +450,7 @@ describe API::Runners do it 'does not enable runner' do post api("/projects/#{project.id}/runners", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -458,7 +458,7 @@ describe API::Runners do it 'does not enable runner' do post api("/projects/#{project.id}/runners") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -470,7 +470,7 @@ describe API::Runners do expect do delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { project.runners.count }.by(-1) end @@ -484,14 +484,14 @@ describe API::Runners do expect do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user) end.to change { project.runners.count }.by(0) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'returns 404 is runner is not found' do delete api("/projects/#{project.id}/runners/9999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -499,7 +499,7 @@ describe API::Runners do it "does not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -507,7 +507,7 @@ describe API::Runners do it "does not disable project's runner" do delete api("/projects/#{project.id}/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 7e174903918..dfe48e45d49 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -16,7 +16,7 @@ describe API::Services do it "updates #{service} settings" do put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) current_service = project.services.first event = current_service.event_names.empty? ? "foo" : current_service.event_names.first @@ -24,7 +24,7 @@ describe API::Services do put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(project.services.first[event]).not_to eq(state) unless event == "foo" end @@ -56,7 +56,7 @@ describe API::Services do it "deletes #{service}" do delete api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end @@ -74,20 +74,20 @@ describe API::Services do it 'returns authentication error when unauthenticated' do get api("/projects/#{project.id}/services/#{dashed_service}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns all properties of service #{service} when authenticated as admin" do get api("/projects/#{project.id}/services/#{dashed_service}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map) end it "returns properties of service #{service} other than passwords when authenticated as project owner" do get api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords) end @@ -95,7 +95,7 @@ describe API::Services do project.team << [user2, :developer] get api("/projects/#{project.id}/services/#{dashed_service}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -108,7 +108,7 @@ describe API::Services do it 'returns a not found message' do post api("/projects/#{project.id}/services/idonotexist/trigger") - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["error"]).to eq("404 Not Found") end end @@ -127,7 +127,7 @@ describe API::Services do it 'when the service is inactive' do post api("/projects/#{project.id}/services/#{service_name}/trigger"), params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -142,7 +142,7 @@ describe API::Services do it 'returns status 200' do post api("/projects/#{project.id}/services/#{service_name}/trigger"), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -150,7 +150,7 @@ describe API::Services do it 'returns a generic 404' do post api("/projects/404/services/#{service_name}/trigger"), params - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to eq("404 Service Not Found") end end @@ -170,7 +170,7 @@ describe API::Services do it 'returns status 200' do post api("/projects/#{project.id}/services/#{service_name}/trigger"), token: 'token', text: 'help' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['response_type']).to eq("ephemeral") end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb deleted file mode 100644 index 5e77519c867..00000000000 --- a/spec/requests/api/session_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'spec_helper' - -describe API::Session do - let(:user) { create(:user) } - - describe "POST /session" do - context "when valid password" do - it "returns private token" do - post api("/session"), email: user.email, password: '12345678' - expect(response).to have_http_status(201) - - expect(json_response['email']).to eq(user.email) - expect(json_response['private_token']).to eq(user.private_token) - expect(json_response['is_admin']).to eq(user.admin?) - expect(json_response['can_create_project']).to eq(user.can_create_project?) - expect(json_response['can_create_group']).to eq(user.can_create_group?) - end - - context 'with 2FA enabled' do - it 'rejects sign in attempts' do - user = create(:user, :two_factor) - - post api('/session'), email: user.email, password: user.password - - expect(response).to have_http_status(401) - expect(response.body).to include('You have 2FA enabled.') - end - end - end - - context 'when email has case-typo and password is valid' do - it 'returns private token' do - post api('/session'), email: user.email.upcase, password: '12345678' - expect(response.status).to eq 201 - - expect(json_response['email']).to eq user.email - expect(json_response['private_token']).to eq user.private_token - expect(json_response['is_admin']).to eq user.admin? - expect(json_response['can_create_project']).to eq user.can_create_project? - expect(json_response['can_create_group']).to eq user.can_create_group? - end - end - - context 'when login has case-typo and password is valid' do - it 'returns private token' do - post api('/session'), login: user.username.upcase, password: '12345678' - expect(response.status).to eq 201 - - expect(json_response['email']).to eq user.email - expect(json_response['private_token']).to eq user.private_token - expect(json_response['is_admin']).to eq user.admin? - expect(json_response['can_create_project']).to eq user.can_create_project? - expect(json_response['can_create_group']).to eq user.can_create_group? - end - end - - context "when invalid password" do - it "returns authentication error" do - post api("/session"), email: user.email, password: '123' - expect(response).to have_http_status(401) - - expect(json_response['email']).to be_nil - expect(json_response['private_token']).to be_nil - end - end - - context "when empty password" do - it "returns authentication error with email" do - post api("/session"), email: user.email - - expect(response).to have_http_status(400) - end - - it "returns authentication error with username" do - post api("/session"), email: user.username - - expect(response).to have_http_status(400) - end - end - - context "when empty name" do - it "returns authentication error" do - post api("/session"), password: user.password - - expect(response).to have_http_status(400) - end - end - - context "when user is blocked" do - it "returns authentication error" do - user.block - post api("/session"), email: user.username, password: user.password - - expect(response).to have_http_status(401) - end - end - - context "when user is ldap_blocked" do - it "returns authentication error" do - user.ldap_block - post api("/session"), email: user.username, password: user.password - - expect(response).to have_http_status(401) - end - end - end -end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 0b9a4b5c3db..5d3e78dd7c8 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -7,7 +7,7 @@ describe API::Settings, 'Settings' do describe "GET /application/settings" do it "returns application settings" do get api("/application/settings", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) expect(json_response['password_authentication_enabled']).to be_truthy @@ -23,6 +23,7 @@ describe API::Settings, 'Settings' do expect(json_response['dsa_key_restriction']).to eq(0) expect(json_response['ecdsa_key_restriction']).to eq(0) expect(json_response['ed25519_key_restriction']).to eq(0) + expect(json_response['circuitbreaker_failure_count_threshold']).not_to be_nil end end @@ -52,9 +53,10 @@ describe API::Settings, 'Settings' do rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE, dsa_key_restriction: 2048, ecdsa_key_restriction: 384, - ed25519_key_restriction: 256 + ed25519_key_restriction: 256, + circuitbreaker_failure_wait_time: 2 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storages']).to eq(['custom']) @@ -73,6 +75,7 @@ describe API::Settings, 'Settings' do expect(json_response['dsa_key_restriction']).to eq(2048) expect(json_response['ecdsa_key_restriction']).to eq(384) expect(json_response['ed25519_key_restriction']).to eq(256) + expect(json_response['circuitbreaker_failure_wait_time']).to eq(2) end end @@ -80,7 +83,7 @@ describe API::Settings, 'Settings' do it "returns a blank parameter error message" do put api("/application/settings", admin), koding_enabled: true - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('koding_url is missing') end end @@ -89,7 +92,7 @@ describe API::Settings, 'Settings' do it "returns a blank parameter error message" do put api("/application/settings", admin), plantuml_enabled: true - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('plantuml_url is missing') end end diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb index 83042d0cb12..fff9adb7f57 100644 --- a/spec/requests/api/sidekiq_metrics_spec.rb +++ b/spec/requests/api/sidekiq_metrics_spec.rb @@ -7,28 +7,28 @@ describe API::SidekiqMetrics do it 'defines the `queue_metrics` endpoint' do get api('/sidekiq/queue_metrics', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash end it 'defines the `process_metrics` endpoint' do get api('/sidekiq/process_metrics', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['processes']).to be_an Array end it 'defines the `job_stats` endpoint' do get api('/sidekiq/job_stats', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash end it 'defines the `compound_metrics` endpoint' do get api('/sidekiq/compound_metrics', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a Hash expect(json_response['queues']).to be_a Hash expect(json_response['processes']).to be_an Array diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index d3905f698bd..74198c8eb4f 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -11,7 +11,7 @@ describe API::Snippets do get api("/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( @@ -27,7 +27,7 @@ describe API::Snippets do get api("/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(0) @@ -46,7 +46,7 @@ describe API::Snippets do it 'returns all snippets with public visibility from all users' do get api("/snippets/public", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( @@ -67,7 +67,7 @@ describe API::Snippets do it 'returns raw text' do get api("/snippets/#{snippet.id}/raw", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end @@ -75,7 +75,7 @@ describe API::Snippets do it 'returns 404 for invalid snippet id' do get api("/snippets/1234/raw", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end @@ -86,7 +86,7 @@ describe API::Snippets do it 'returns snippet json' do get api("/snippets/#{snippet.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(snippet.title) expect(json_response['description']).to eq(snippet.description) @@ -96,7 +96,7 @@ describe API::Snippets do it 'returns 404 for invalid snippet id' do get api("/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end end @@ -117,7 +117,7 @@ describe API::Snippets do post api("/snippets/", user), params end.to change { PersonalSnippet.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(params[:title]) expect(json_response['description']).to eq(params[:description]) expect(json_response['file_name']).to eq(params[:file_name]) @@ -128,7 +128,7 @@ describe API::Snippets do post api("/snippets/", user), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -152,7 +152,7 @@ describe API::Snippets do expect { create_snippet(visibility: 'public') } .not_to change { Snippet.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -177,7 +177,7 @@ describe API::Snippets do put api("/snippets/#{snippet.id}", user), content: new_content, description: new_description - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) snippet.reload expect(snippet.content).to eq(new_content) expect(snippet.description).to eq(new_description) @@ -186,21 +186,21 @@ describe API::Snippets do it 'returns 404 for invalid snippet id' do put api("/snippets/1234", user), title: 'foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it "returns 404 for another user's snippet" do put api("/snippets/#{snippet.id}", other_user), title: 'fubar' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it 'returns 400 for missing parameters' do put api("/snippets/1234", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -228,7 +228,7 @@ describe API::Snippets do expect { update_snippet(title: 'Foo') } .not_to change { snippet.reload.title } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -260,14 +260,14 @@ describe API::Snippets do expect do delete api("/snippets/#{public_snippet.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { PersonalSnippet.count }.by(-1) end it 'returns 404 for invalid snippet id' do delete api("/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end @@ -284,7 +284,7 @@ describe API::Snippets do it 'exposes known attributes' do get api("/snippets/#{snippet.id}/user_agent_detail", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) expect(json_response['ip_address']).to eq(user_agent_detail.ip_address) expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted) @@ -293,7 +293,7 @@ describe API::Snippets do it "returns unautorized for non-admin users" do get api("/snippets/#{snippet.id}/user_agent_detail", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 216d278ad21..c7a009e999e 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -14,7 +14,7 @@ describe API::SystemHooks do it "returns authentication error" do get api("/hooks") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -22,7 +22,7 @@ describe API::SystemHooks do it "returns forbidden error" do get api("/hooks", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -30,7 +30,7 @@ describe API::SystemHooks do it "returns an array of hooks" do get api("/hooks", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['url']).to eq(hook.url) @@ -51,13 +51,13 @@ describe API::SystemHooks do it "responds with 400 if url not given" do post api("/hooks", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "responds with 400 if url is invalid" do post api("/hooks", admin), url: 'hp://mep.mep' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "does not create new hook without url" do @@ -69,7 +69,7 @@ describe API::SystemHooks do it 'sets default values for events' do post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['enable_ssl_verification']).to be true expect(json_response['tag_push_events']).to be false end @@ -78,13 +78,13 @@ describe API::SystemHooks do describe "GET /hooks/:id" do it "returns hook by id" do get api("/hooks/#{hook.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['event_name']).to eq('project_create') end it "returns 404 on failure" do get api("/hooks/404", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -93,14 +93,14 @@ describe API::SystemHooks do expect do delete api("/hooks/#{hook.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { SystemHook.count }.by(-1) end it 'returns 404 if the system hook does not exist' do delete api('/hooks/12345', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index f8af9295842..de1619f33c1 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -23,7 +23,7 @@ describe API::Templates do it 'returns a list of available gitignore templates' do get api('/templates/gitignores') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to be > 15 @@ -34,7 +34,7 @@ describe API::Templates do it 'returns a list of available gitlab_ci_ymls' do get api('/templates/gitlab_ci_ymls') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['name']).not_to be_nil @@ -45,7 +45,7 @@ describe API::Templates do it 'adds a disclaimer on the top' do get api('/templates/gitlab_ci_ymls/Ruby') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['content']).to start_with("# This file is a template,") end end @@ -74,7 +74,7 @@ describe API::Templates do it 'returns a list of available license templates' do get api('/templates/licenses') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(12) @@ -86,7 +86,7 @@ describe API::Templates do it 'returns a list of available popular license templates' do get api('/templates/licenses?popular=1') - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(3) @@ -169,7 +169,7 @@ describe API::Templates do let(:license_type) { 'muth-over9000' } it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 25d7f6dffcf..c6063a2e089 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -110,7 +110,7 @@ describe API::Todos do it 'returns authentication error' do post api("/todos/#{pending_1.id}/mark_as_done") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -118,7 +118,7 @@ describe API::Todos do it 'marks a todo as done' do post api("/todos/#{pending_1.id}/mark_as_done", john_doe) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(pending_1.id) expect(json_response['state']).to eq('done') expect(pending_1.reload).to be_done @@ -137,7 +137,7 @@ describe API::Todos do it 'returns authentication error' do post api('/todos/mark_as_done') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -145,7 +145,7 @@ describe API::Todos do it 'marks all todos as done' do post api('/todos/mark_as_done', john_doe) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(pending_1.reload).to be_done expect(pending_2.reload).to be_done expect(pending_3.reload).to be_done @@ -196,9 +196,9 @@ describe API::Todos do post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", guest) if issuable_type == 'merge_requests' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) else - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 922b99a6cba..b2c56f7af2c 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -28,13 +28,13 @@ describe API::Triggers do it 'returns bad request if token is missing' do post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns not found if project is not found' do post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -44,7 +44,7 @@ describe API::Triggers do it 'creates pipeline' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to include('id' => pipeline.id) pipeline.builds.reload expect(pipeline.builds.pending.size).to eq(2) @@ -54,7 +54,7 @@ describe API::Triggers do it 'returns bad request with no pipeline created if there\'s no commit for that ref' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('base' => ["Reference not found"]) end @@ -66,21 +66,21 @@ describe API::Triggers do it 'validates variables to be a hash' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('variables is invalid') end it 'validates variables needs to be a map of key-valued strings' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'creates trigger request with variables' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables) end end @@ -93,7 +93,7 @@ describe API::Triggers do it 'creates pipeline' do post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to include('id' => pipeline.id) pipeline.builds.reload expect(pipeline.builds.pending.size).to eq(2) @@ -106,7 +106,7 @@ describe API::Triggers do it 'does not leak the presence of project when token is for different project' do post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'creates builds from the ref given in the URL, not in the body' do @@ -114,7 +114,7 @@ describe API::Triggers do post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(5) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when ref contains a dot' do @@ -125,7 +125,7 @@ describe API::Triggers do post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(4) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end end @@ -136,7 +136,7 @@ describe API::Triggers do it 'returns list of triggers' do get api("/projects/#{project.id}/triggers", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_a(Array) expect(json_response[0]).to have_key('token') @@ -147,7 +147,7 @@ describe API::Triggers do it 'does not return triggers list' do get api("/projects/#{project.id}/triggers", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -155,7 +155,7 @@ describe API::Triggers do it 'does not return triggers list' do get api("/projects/#{project.id}/triggers") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -165,14 +165,14 @@ describe API::Triggers do it 'returns trigger details' do get api("/projects/#{project.id}/triggers/#{trigger.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a(Hash) end it 'responds with 404 Not Found if requesting non-existing trigger' do get api("/projects/#{project.id}/triggers/-5", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -180,7 +180,7 @@ describe API::Triggers do it 'does not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -188,7 +188,7 @@ describe API::Triggers do it 'does not return triggers list' do get api("/projects/#{project.id}/triggers/#{trigger.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -202,7 +202,7 @@ describe API::Triggers do description: 'trigger' end.to change {project.triggers.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to include('description' => 'trigger') end end @@ -211,7 +211,7 @@ describe API::Triggers do it 'does not create trigger' do post api("/projects/#{project.id}/triggers", user) - expect(response).to have_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:bad_request) end end end @@ -221,7 +221,7 @@ describe API::Triggers do post api("/projects/#{project.id}/triggers", user2), description: 'trigger' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -230,7 +230,7 @@ describe API::Triggers do post api("/projects/#{project.id}/triggers"), description: 'trigger' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -243,7 +243,7 @@ describe API::Triggers do put api("/projects/#{project.id}/triggers/#{trigger.id}", user), description: new_description - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to include('description' => new_description) expect(trigger.reload.description).to eq(new_description) end @@ -253,7 +253,7 @@ describe API::Triggers do it 'does not update trigger' do put api("/projects/#{project.id}/triggers/#{trigger.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -261,7 +261,7 @@ describe API::Triggers do it 'does not update trigger' do put api("/projects/#{project.id}/triggers/#{trigger.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -271,7 +271,7 @@ describe API::Triggers do it 'updates owner' do post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to include('owner') expect(trigger.reload.owner).to eq(user) end @@ -281,7 +281,7 @@ describe API::Triggers do it 'does not update owner' do post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -289,7 +289,7 @@ describe API::Triggers do it 'does not update owner' do post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -300,14 +300,14 @@ describe API::Triggers do expect do delete api("/projects/#{project.id}/triggers/#{trigger.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change {project.triggers.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing trigger' do delete api("/projects/#{project.id}/triggers/-5", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it_behaves_like '412 response' do @@ -319,7 +319,7 @@ describe API::Triggers do it 'does not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -327,7 +327,7 @@ describe API::Triggers do it 'does not delete trigger' do delete api("/projects/#{project.id}/triggers/#{trigger.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 5b306ec6cbf..634c8dae0ba 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -113,7 +113,7 @@ describe API::Users do it "returns a 403 when non-admin user searches by external UID" do get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}&provider=#{omniauth_user.identities.first.provider}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not reveal the `is_admin` flag of the user' do @@ -125,6 +125,15 @@ describe API::Users do end context "when admin" do + context 'when sudo is defined' do + it 'does not return 500' do + admin_personal_access_token = create(:personal_access_token, user: admin, scopes: [:sudo]) + get api("/users?sudo=#{user.id}", admin, personal_access_token: admin_personal_access_token) + + expect(response).to have_gitlab_http_status(:success) + end + end + it "returns an array of users" do get api("/users", admin) @@ -153,13 +162,13 @@ describe API::Users do it "returns 400 error if provider with no extern_uid" do get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 400 error if provider with no extern_uid" do get api("/users?provider=#{omniauth_user.identities.first.provider}", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a user created before a specific date" do @@ -231,21 +240,21 @@ describe API::Users do get api("/users/#{user.id}") - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end it "returns a 404 error if user id not found" do get api("/users/9999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -262,7 +271,7 @@ describe API::Users do it "creates user with correct attributes" do post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -276,12 +285,12 @@ describe API::Users do post api('/users', admin), attributes - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it "creates non-admin user" do post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -291,7 +300,7 @@ describe API::Users do it "creates non-admin users by default" do post api('/users', admin), attributes_for(:user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) expect(new_user).not_to eq(nil) @@ -300,12 +309,12 @@ describe API::Users do it "returns 201 Created on success" do post api("/users", admin), attributes_for(:user, projects_limit: 3) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'creates non-external users by default' do post api("/users", admin), attributes_for(:user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -315,7 +324,7 @@ describe API::Users do it 'allows an external user to be created' do post api("/users", admin), attributes_for(:user, external: true) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -326,7 +335,7 @@ describe API::Users do it "creates user with reset password" do post api('/users', admin), attributes_for(:user, reset_password: true).except(:password) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) user_id = json_response['id'] new_user = User.find(user_id) @@ -340,27 +349,27 @@ describe API::Users do email: 'invalid email', password: 'password', name: 'test' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if name not given' do post api('/users', admin), attributes_for(:user).except(:name) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if password not given' do post api('/users', admin), attributes_for(:user).except(:password) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if email not given' do post api('/users', admin), attributes_for(:user).except(:email) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if username not given' do post api('/users', admin), attributes_for(:user).except(:username) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 error if user does not validate' do @@ -371,7 +380,7 @@ describe API::Users do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['password']) .to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']) @@ -384,7 +393,7 @@ describe API::Users do it "is not available for non admin users" do post api("/users", user), attributes_for(:user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context 'with existing user' do @@ -404,7 +413,7 @@ describe API::Users do password: 'password', username: 'foo' end.to change { User.count }.by(0) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Email has already been taken') end @@ -416,14 +425,14 @@ describe API::Users do password: 'password', username: 'test' end.to change { User.count }.by(0) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to eq('Username has already been taken') end it 'creates user with new identity' do post api("/users", admin), attributes_for(:user, provider: 'github', extern_uid: '67890') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['identities'].first['extern_uid']).to eq('67890') expect(json_response['identities'].first['provider']).to eq('github') end @@ -441,7 +450,7 @@ describe API::Users do describe "GET /users/sign_up" do it "redirects to sign in page" do get "/users/sign_up" - expect(response).to have_http_status(302) + expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(new_user_session_path) end end @@ -456,7 +465,7 @@ describe API::Users do it "updates user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['bio']).to eq('new test bio') expect(user.reload.bio).to eq('new test bio') end @@ -464,14 +473,14 @@ describe API::Users do it "updates user with new password and forces reset on next login" do put api("/users/#{user.id}", admin), password: '12345678' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.password_expires_at).to be <= Time.now end it "updates user with organization" do put api("/users/#{user.id}", admin), { organization: 'GitLab' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['organization']).to eq('GitLab') expect(user.reload.organization).to eq('GitLab') end @@ -482,14 +491,14 @@ describe API::Users do user.reload expect(user.avatar).to be_present - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['avatar_url']).to include(user.avatar_path) end it 'updates user with his own email' do put api("/users/#{user.id}", admin), email: user.email - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['email']).to eq(user.email) expect(user.reload.email).to eq(user.email) end @@ -497,14 +506,14 @@ describe API::Users do it 'updates user with a new email' do put api("/users/#{user.id}", admin), email: 'new@email.com' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.notification_email).to eq('new@email.com') end it 'updates user with his own username' do put api("/users/#{user.id}", admin), username: user.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['username']).to eq(user.username) expect(user.reload.username).to eq(user.username) end @@ -512,14 +521,14 @@ describe API::Users do it "updates user's existing identity" do put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321') end it 'updates user with new identity' do put api("/users/#{user.id}", admin), provider: 'github', extern_uid: 'john' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.identities.first.extern_uid).to eq('john') expect(user.reload.identities.first.provider).to eq('github') end @@ -527,7 +536,7 @@ describe API::Users do it "updates admin status" do put api("/users/#{user.id}", admin), { admin: true } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.admin).to eq(true) end @@ -542,7 +551,7 @@ describe API::Users do it "does not update admin status" do put api("/users/#{admin_user.id}", admin), { can_create_group: false } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(admin_user.reload.admin).to eq(true) expect(admin_user.can_create_group).to eq(false) end @@ -550,7 +559,7 @@ describe API::Users do it "does not allow invalid update" do put api("/users/#{user.id}", admin), { email: 'invalid email' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(user.reload.email).not_to eq('invalid email') end @@ -560,21 +569,21 @@ describe API::Users do put api("/users/#{user.id}", user), attributes_for(:user) end.not_to change { user.reload.attributes } - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it "returns 404 for non-existing user" do put api("/users/999999", admin), { bio: 'update should fail' } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 if invalid ID" do put api("/users/ASDF", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 400 error if user does not validate' do @@ -585,7 +594,7 @@ describe API::Users do name: 'test', bio: 'g' * 256, projects_limit: -1 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['password']) .to eq(['is too short (minimum is 8 characters)']) expect(json_response['message']['bio']) @@ -599,13 +608,13 @@ describe API::Users do it 'returns 400 if provider is missing for identity update' do put api("/users/#{omniauth_user.id}", admin), extern_uid: '654321' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 if external UID is missing for identity update' do put api("/users/#{omniauth_user.id}", admin), provider: 'ldap' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "with existing user" do @@ -618,7 +627,7 @@ describe API::Users do it 'returns 409 conflict error if email address exists' do put api("/users/#{@user.id}", admin), email: 'test@example.com' - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(@user.reload.email).to eq(@user.email) end @@ -626,7 +635,7 @@ describe API::Users do @user_id = User.all.last.id put api("/users/#{@user.id}", admin), username: 'test' - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(@user.reload.username).to eq(@user.username) end end @@ -640,14 +649,14 @@ describe API::Users do it "does not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create key without title' do post api("/users/#{user.id}/keys", admin), key: 'some key' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') end @@ -660,7 +669,7 @@ describe API::Users do it "returns 400 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -672,14 +681,14 @@ describe API::Users do context 'when unauthenticated' do it 'returns authentication error' do get api("/users/#{user.id}/keys") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated' do it 'returns 404 for non-existing user' do get api('/users/999999/keys', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -689,7 +698,7 @@ describe API::Users do get api("/users/#{user.id}/keys", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['title']).to eq(key.title) @@ -705,7 +714,7 @@ describe API::Users do context 'when unauthenticated' do it 'returns authentication error' do delete api("/users/#{user.id}/keys/42") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -717,7 +726,7 @@ describe API::Users do expect do delete api("/users/#{user.id}/keys/#{key.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.keys.count }.by(-1) end @@ -729,13 +738,13 @@ describe API::Users do user.keys << key user.save delete api("/users/999999/keys/#{key.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do delete api("/users/#{user.id}/keys/42", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end end @@ -749,7 +758,7 @@ describe API::Users do it 'does not create invalid GPG key' do post api("/users/#{user.id}/gpg_keys", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end @@ -758,14 +767,14 @@ describe API::Users do expect do post api("/users/#{user.id}/gpg_keys", admin), key_attrs - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { user.gpg_keys.count }.by(1) end it 'returns 400 for invalid ID' do post api('/users/999999/gpg_keys', admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -778,7 +787,7 @@ describe API::Users do it 'returns authentication error' do get api("/users/#{user.id}/gpg_keys") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -786,14 +795,14 @@ describe API::Users do it 'returns 404 for non-existing user' do get api('/users/999999/gpg_keys', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do delete api("/users/#{user.id}/gpg_keys/42", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end @@ -803,7 +812,7 @@ describe API::Users do get api("/users/#{user.id}/gpg_keys", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['key']).to eq(gpg_key.key) @@ -820,7 +829,7 @@ describe API::Users do it 'returns authentication error' do delete api("/users/#{user.id}/keys/42") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -832,7 +841,7 @@ describe API::Users do expect do delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.gpg_keys.count }.by(-1) end @@ -842,14 +851,14 @@ describe API::Users do delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do delete api("/users/#{user.id}/gpg_keys/42", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end end @@ -864,7 +873,7 @@ describe API::Users do it 'returns authentication error' do post api("/users/#{user.id}/gpg_keys/42/revoke") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -876,7 +885,7 @@ describe API::Users do expect do post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin) - expect(response).to have_http_status(:accepted) + expect(response).to have_gitlab_http_status(:accepted) end.to change { user.gpg_keys.count }.by(-1) end @@ -886,14 +895,14 @@ describe API::Users do post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do post api("/users/#{user.id}/gpg_keys/42/revoke", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end end @@ -907,7 +916,7 @@ describe API::Users do it "does not create invalid email" do post api("/users/#{user.id}/emails", admin), {} - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('email is missing') end @@ -921,7 +930,7 @@ describe API::Users do it "returns a 400 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -933,14 +942,14 @@ describe API::Users do context 'when unauthenticated' do it 'returns authentication error' do get api("/users/#{user.id}/emails") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated' do it 'returns 404 for non-existing user' do get api('/users/999999/emails', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -950,7 +959,7 @@ describe API::Users do get api("/users/#{user.id}/emails", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['email']).to eq(email.email) @@ -959,7 +968,7 @@ describe API::Users do it "returns a 404 for invalid ID" do get api("/users/ASDF/emails", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -972,7 +981,7 @@ describe API::Users do context 'when unauthenticated' do it 'returns authentication error' do delete api("/users/#{user.id}/emails/42") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -984,7 +993,7 @@ describe API::Users do expect do delete api("/users/#{user.id}/emails/#{email.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.emails.count }.by(-1) end @@ -996,20 +1005,20 @@ describe API::Users do user.emails << email user.save delete api("/users/999999/emails/#{email.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if email not foud' do delete api("/users/#{user.id}/emails/42", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end it "returns a 404 for invalid ID" do delete api("/users/ASDF/emails/bar", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1025,7 +1034,7 @@ describe API::Users do it "deletes user" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) } - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound end @@ -1036,31 +1045,31 @@ describe API::Users do it "does not delete for unauthenticated user" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}") } - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "is not available for non admin users" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}", user) } - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns 404 for non-existing user" do Sidekiq::Testing.inline! { delete api("/users/999999", admin) } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do Sidekiq::Testing.inline! { delete api("/users/ASDF", admin) } - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "hard delete disabled" do it "moves contributions to the ghost user" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) } - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(issue.reload).to be_persisted expect(issue.author.ghost?).to be_truthy end @@ -1070,7 +1079,7 @@ describe API::Users do it "removes contributions" do Sidekiq::Testing.inline! { delete api("/users/#{user.id}?hard_delete=true", admin) } - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(Issue.exists?(issue.id)).to be_falsy end end @@ -1084,22 +1093,14 @@ describe API::Users do it 'returns 403 without private token when sudo is defined' do get api("/user?private_token=#{personal_access_token}&sudo=123") - expect(response).to have_http_status(403) - end - end - - context 'with private token' do - it 'returns 403 without private token when sudo defined' do - get api("/user?private_token=#{user.private_token}&sudo=123") - - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'returns current user without private token when sudo not defined' do get api("/user", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/user/public') expect(json_response['id']).to eq(user.id) end @@ -1119,31 +1120,13 @@ describe API::Users do it 'returns 403 without private token when sudo defined' do get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}") - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin_personal_access_token}") - expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/admin') - expect(json_response['id']).to eq(admin.id) - end - end - - context 'with private token' do - it 'returns sudoed user with private token when sudo defined' do - get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}") - - expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/login') - expect(json_response['id']).to eq(user.id) - end - - it 'returns initial current user without private token but with is_admin when sudo not defined' do - get api("/user?private_token=#{admin.private_token}") - - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end @@ -1154,7 +1137,7 @@ describe API::Users do it "returns 401 error if user is unauthenticated" do get api("/user") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -1163,7 +1146,7 @@ describe API::Users do context "when unauthenticated" do it "returns authentication error" do get api("/user/keys") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -1174,7 +1157,7 @@ describe API::Users do get api("/user/keys", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) @@ -1194,14 +1177,14 @@ describe API::Users do user.keys << key user.save get api("/user/keys/#{key.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["title"]).to eq(key.title) end it "returns 404 Not Found within invalid ID" do get api("/user/keys/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end @@ -1210,14 +1193,14 @@ describe API::Users do user.save admin get api("/user/keys/#{key.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end it "returns 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "scopes" do @@ -1234,31 +1217,31 @@ describe API::Users do expect do post api("/user/keys", user), key_attrs end.to change { user.keys.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it "returns a 401 error if unauthorized" do post api("/user/keys"), title: 'some title', key: 'some key' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "does not create ssh key without key" do post api("/user/keys", user), title: 'title' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'does not create ssh key without title' do post api('/user/keys', user), key: 'some key' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') end it "does not create ssh key without title" do post api("/user/keys", user), key: "somekey" - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -1270,7 +1253,7 @@ describe API::Users do expect do delete api("/user/keys/#{key.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.keys.count}.by(-1) end @@ -1281,7 +1264,7 @@ describe API::Users do it "returns 404 if key ID not found" do delete api("/user/keys/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Key Not Found') end @@ -1289,13 +1272,13 @@ describe API::Users do user.keys << key user.save delete api("/user/keys/#{key.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns a 404 for invalid ID" do delete api("/users/keys/ASDF", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1304,7 +1287,7 @@ describe API::Users do it 'returns authentication error' do get api('/user/gpg_keys') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -1315,7 +1298,7 @@ describe API::Users do get api('/user/gpg_keys', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['key']).to eq(gpg_key.key) @@ -1337,14 +1320,14 @@ describe API::Users do get api("/user/gpg_keys/#{gpg_key.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['key']).to eq(gpg_key.key) end it 'returns 404 Not Found within invalid ID' do get api('/user/gpg_keys/42', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end @@ -1354,14 +1337,14 @@ describe API::Users do get api("/user/gpg_keys/#{gpg_key.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end it 'returns 404 for invalid ID' do get api('/users/gpg_keys/ASDF', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'scopes' do @@ -1378,20 +1361,20 @@ describe API::Users do expect do post api('/user/gpg_keys', user), key_attrs - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { user.gpg_keys.count }.by(1) end it 'returns a 401 error if unauthorized' do post api('/user/gpg_keys'), key: 'some key' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'does not create GPG key without key' do post api('/user/gpg_keys', user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end end @@ -1404,14 +1387,14 @@ describe API::Users do expect do post api("/user/gpg_keys/#{gpg_key.id}/revoke", user) - expect(response).to have_http_status(:accepted) + expect(response).to have_gitlab_http_status(:accepted) end.to change { user.gpg_keys.count}.by(-1) end it 'returns 404 if key ID not found' do post api('/user/gpg_keys/42/revoke', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end @@ -1421,13 +1404,13 @@ describe API::Users do post api("/user/gpg_keys/#{gpg_key.id}/revoke") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 404 for invalid ID' do post api('/users/gpg_keys/ASDF/revoke', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1439,14 +1422,14 @@ describe API::Users do expect do delete api("/user/gpg_keys/#{gpg_key.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.gpg_keys.count}.by(-1) end it 'returns 404 if key ID not found' do delete api('/user/gpg_keys/42', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 GPG Key Not Found') end @@ -1456,13 +1439,13 @@ describe API::Users do delete api("/user/gpg_keys/#{gpg_key.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 404 for invalid ID' do delete api('/users/gpg_keys/ASDF', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1470,7 +1453,7 @@ describe API::Users do context "when unauthenticated" do it "returns authentication error" do get api("/user/emails") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -1481,7 +1464,7 @@ describe API::Users do get api("/user/emails", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) @@ -1501,13 +1484,13 @@ describe API::Users do user.emails << email user.save get api("/user/emails/#{email.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["email"]).to eq(email.email) end it "returns 404 Not Found within invalid ID" do get api("/user/emails/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end @@ -1516,14 +1499,14 @@ describe API::Users do user.save admin get api("/user/emails/#{email.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end it "returns 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "scopes" do @@ -1540,18 +1523,18 @@ describe API::Users do expect do post api("/user/emails", user), email_attrs end.to change { user.emails.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it "returns a 401 error if unauthorized" do post api("/user/emails"), email: 'some email' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "does not create email with invalid email" do post api("/user/emails", user), {} - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('email is missing') end end @@ -1564,7 +1547,7 @@ describe API::Users do expect do delete api("/user/emails/#{email.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { user.emails.count}.by(-1) end @@ -1575,7 +1558,7 @@ describe API::Users do it "returns 404 if email ID not found" do delete api("/user/emails/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Email Not Found') end @@ -1583,13 +1566,13 @@ describe API::Users do user.emails << email user.save delete api("/user/emails/#{email.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns 400 for invalid ID" do delete api("/user/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -1600,25 +1583,25 @@ describe API::Users do it 'blocks existing user' do post api("/users/#{user.id}/block", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(user.reload.state).to eq('blocked') end it 'does not re-block ldap blocked users' do post api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'does not be available for non admin users' do post api("/users/#{user.id}/block", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(user.reload.state).to eq('active') end it 'returns a 404 error if user id not found' do post api('/users/9999/block', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end @@ -1632,38 +1615,38 @@ describe API::Users do it 'unblocks existing user' do post api("/users/#{user.id}/unblock", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(user.reload.state).to eq('active') end it 'unblocks a blocked user' do post api("/users/#{blocked_user.id}/unblock", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(blocked_user.reload.state).to eq('active') end it 'does not unblock ldap blocked users' do post api("/users/#{ldap_blocked_user.id}/unblock", admin) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'does not be available for non admin users' do post api("/users/#{user.id}/unblock", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(user.reload.state).to eq('active') end it 'returns a 404 error if user id not found' do post api('/users/9999/block', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do post api("/users/ASDF/block", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1675,7 +1658,7 @@ describe API::Users do it 'has no permission' do get api("/user/activities", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1720,21 +1703,21 @@ describe API::Users do it 'returns a 404 error if user not found' do get api("/users/#{not_existing_user_id}/impersonation_tokens", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns a 403 error when authenticated as normal user' do get api("/users/#{not_existing_user_id}/impersonation_tokens", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response['message']).to eq('403 Forbidden') end it 'returns an array of all impersonated tokens' do get api("/users/#{user.id}/impersonation_tokens", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) @@ -1743,7 +1726,7 @@ describe API::Users do it 'returns an array of active impersonation tokens if state active' do get api("/users/#{user.id}/impersonation_tokens?state=active", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -1753,7 +1736,7 @@ describe API::Users do it 'returns an array of inactive personal access tokens if active is set to false' do get api("/users/#{user.id}/impersonation_tokens?state=inactive", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response).to all(include('active' => false)) @@ -1769,7 +1752,7 @@ describe API::Users do it 'returns validation error if impersonation token misses some attributes' do post api("/users/#{user.id}/impersonation_tokens", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('name is missing') end @@ -1778,7 +1761,7 @@ describe API::Users do name: name, expires_at: expires_at - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1787,7 +1770,7 @@ describe API::Users do name: name, expires_at: expires_at - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response['message']).to eq('403 Forbidden') end @@ -1798,7 +1781,7 @@ describe API::Users do scopes: scopes, impersonation: impersonation - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq(name) expect(json_response['scopes']).to eq(scopes) expect(json_response['expires_at']).to eq(expires_at) @@ -1818,35 +1801,35 @@ describe API::Users do it 'returns 404 error if user not found' do get api("/users/#{not_existing_user_id}/impersonation_tokens/1", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns a 404 error if impersonation token not found' do get api("/users/#{user.id}/impersonation_tokens/#{not_existing_pat_id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 404 error if token is not impersonation token' do get api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 403 error when authenticated as normal user' do get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response['message']).to eq('403 Forbidden') end it 'returns a personal access token' do get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['token']).to be_present expect(json_response['impersonation']).to be_truthy end @@ -1859,28 +1842,28 @@ describe API::Users do it 'returns a 404 error if user not found' do delete api("/users/#{not_existing_user_id}/impersonation_tokens/1", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it 'returns a 404 error if impersonation token not found' do delete api("/users/#{user.id}/impersonation_tokens/#{not_existing_pat_id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 404 error if token is not impersonation token' do delete api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 403 error when authenticated as normal user' do delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response['message']).to eq('403 Forbidden') end @@ -1891,9 +1874,13 @@ describe API::Users do it 'revokes a impersonation token' do delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(impersonation_token.revoked).to be_falsey expect(impersonation_token.reload.revoked).to be_truthy end end + + include_examples 'custom attributes endpoints', 'users' do + let(:attributable) { user } + end end diff --git a/spec/requests/api/v3/award_emoji_spec.rb b/spec/requests/api/v3/award_emoji_spec.rb index 36d793f505d..0cd8b70007f 100644 --- a/spec/requests/api/v3/award_emoji_spec.rb +++ b/spec/requests/api/v3/award_emoji_spec.rb @@ -16,7 +16,7 @@ describe API::V3::AwardEmoji do it "returns an array of award_emoji" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award_emoji.name) end @@ -24,7 +24,7 @@ describe API::V3::AwardEmoji do it "returns a 404 error when issue id not found" do get v3_api("/projects/#{project.id}/issues/12345/award_emoji", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -32,7 +32,7 @@ describe API::V3::AwardEmoji do it "returns an array of award_emoji" do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['name']).to eq(downvote.name) @@ -46,7 +46,7 @@ describe API::V3::AwardEmoji do it 'returns the awarded emoji' do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(award.name) end @@ -58,7 +58,7 @@ describe API::V3::AwardEmoji do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -69,7 +69,7 @@ describe API::V3::AwardEmoji do it 'returns an array of award emoji' do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(rocket.name) end @@ -80,7 +80,7 @@ describe API::V3::AwardEmoji do it "returns the award emoji" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(award_emoji.name) expect(json_response['awardable_id']).to eq(issue.id) expect(json_response['awardable_type']).to eq("Issue") @@ -89,7 +89,7 @@ describe API::V3::AwardEmoji do it "returns a 404 error if the award is not found" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -97,7 +97,7 @@ describe API::V3::AwardEmoji do it 'returns the award emoji' do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(downvote.name) expect(json_response['awardable_id']).to eq(merge_request.id) expect(json_response['awardable_type']).to eq("MergeRequest") @@ -111,7 +111,7 @@ describe API::V3::AwardEmoji do it 'returns the awarded emoji' do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(award.name) expect(json_response['awardable_id']).to eq(snippet.id) expect(json_response['awardable_type']).to eq("Snippet") @@ -124,7 +124,7 @@ describe API::V3::AwardEmoji do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -135,7 +135,7 @@ describe API::V3::AwardEmoji do it 'returns an award emoji' do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).not_to be_an Array expect(json_response['name']).to eq(rocket.name) end @@ -148,7 +148,7 @@ describe API::V3::AwardEmoji do it "creates a new award emoji" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'blowfish' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -156,19 +156,19 @@ describe API::V3::AwardEmoji do it "returns a 400 bad request error if the name is not given" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if the user is not authenticated" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it "returns a 404 error if the user authored issue" do post v3_api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "normalizes +1 as thumbsup award" do @@ -182,7 +182,7 @@ describe API::V3::AwardEmoji do post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' post v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") end end @@ -194,7 +194,7 @@ describe API::V3::AwardEmoji do post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('blowfish') expect(json_response['user']['username']).to eq(user.username) end @@ -209,14 +209,14 @@ describe API::V3::AwardEmoji do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' end.to change { note.award_emoji.count }.from(0).to(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['user']['username']).to eq(user.username) end it "it returns 404 error when user authored note" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "normalizes +1 as thumbsup award" do @@ -230,7 +230,7 @@ describe API::V3::AwardEmoji do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response["message"]).to match("has already been taken") end end @@ -242,14 +242,14 @@ describe API::V3::AwardEmoji do expect do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/#{award_emoji.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { issue.award_emoji.count }.from(1).to(0) end it 'returns a 404 error when the award emoji can not be found' do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/award_emoji/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -258,14 +258,14 @@ describe API::V3::AwardEmoji do expect do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/award_emoji/#{downvote.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { merge_request.award_emoji.count }.from(1).to(0) end it 'returns a 404 error when note id not found' do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -277,7 +277,7 @@ describe API::V3::AwardEmoji do expect do delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji/#{award.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { snippet.award_emoji.count }.from(1).to(0) end end @@ -290,7 +290,7 @@ describe API::V3::AwardEmoji do expect do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji/#{rocket.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { note.award_emoji.count }.from(1).to(0) end end diff --git a/spec/requests/api/v3/boards_spec.rb b/spec/requests/api/v3/boards_spec.rb index ea2627142bf..14409d25544 100644 --- a/spec/requests/api/v3/boards_spec.rb +++ b/spec/requests/api/v3/boards_spec.rb @@ -38,7 +38,7 @@ describe API::V3::Boards do it "returns authentication error" do get v3_api(base_url) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -46,7 +46,7 @@ describe API::V3::Boards do it "returns the project issue board" do get v3_api(base_url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(board.id) @@ -63,7 +63,7 @@ describe API::V3::Boards do it 'returns issue board lists' do get v3_api(base_url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['label']['name']).to eq(dev_label.title) @@ -72,7 +72,7 @@ describe API::V3::Boards do it 'returns 404 if board not found' do get v3_api("/projects/#{project.id}/boards/22343/lists", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -82,19 +82,19 @@ describe API::V3::Boards do it "rejects a non member from deleting a list" do delete v3_api("#{base_url}/#{dev_list.id}", non_member) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "rejects a user with guest role from deleting a list" do delete v3_api("#{base_url}/#{dev_list.id}", guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns 404 error if list id not found" do delete v3_api("#{base_url}/44444", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "when the user is project owner" do @@ -107,7 +107,7 @@ describe API::V3::Boards do it "deletes the list if an admin requests it" do delete v3_api("#{base_url}/#{dev_list.id}", owner) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end diff --git a/spec/requests/api/v3/branches_spec.rb b/spec/requests/api/v3/branches_spec.rb index 9cd11a67712..1e038595a1f 100644 --- a/spec/requests/api/v3/branches_spec.rb +++ b/spec/requests/api/v3/branches_spec.rb @@ -17,7 +17,7 @@ describe API::V3::Branches do get v3_api("/projects/#{project.id}/repository/branches", user), per_page: 100 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array branch_names = json_response.map { |x| x['name'] } expect(branch_names).to match_array(project.repository.branch_names) @@ -32,20 +32,20 @@ describe API::V3::Branches do it "removes branch" do delete v3_api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['branch_name']).to eq(branch_name) end it "removes a branch with dots in the branch name" do delete v3_api("/projects/#{project.id}/repository/branches/with.1.2.3", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['branch_name']).to eq("with.1.2.3") end it 'returns 404 if branch not exists' do delete v3_api("/projects/#{project.id}/repository/branches/foobar", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -57,13 +57,13 @@ describe API::V3::Branches do it 'returns 200' do delete v3_api("/projects/#{project.id}/repository/merged_branches", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns a 403 error if guest' do delete v3_api("/projects/#{project.id}/repository/merged_branches", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -73,7 +73,7 @@ describe API::V3::Branches do branch_name: 'feature1', ref: branch_sha - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('feature1') expect(json_response['commit']['id']).to eq(branch_sha) @@ -83,14 +83,14 @@ describe API::V3::Branches do post v3_api("/projects/#{project.id}/repository/branches", user2), branch_name: branch_name, ref: branch_sha - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns 400 if branch name is invalid' do post v3_api("/projects/#{project.id}/repository/branches", user), branch_name: 'new design', ref: branch_sha - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch name is invalid') end @@ -98,13 +98,13 @@ describe API::V3::Branches do post v3_api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) post v3_api("/projects/#{project.id}/repository/branches", user), branch_name: 'new_design1', ref: branch_sha - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Branch already exists') end @@ -113,7 +113,7 @@ describe API::V3::Branches do branch_name: 'new_design3', ref: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Invalid reference name') end end diff --git a/spec/requests/api/v3/broadcast_messages_spec.rb b/spec/requests/api/v3/broadcast_messages_spec.rb index d04b1c72004..d9641011491 100644 --- a/spec/requests/api/v3/broadcast_messages_spec.rb +++ b/spec/requests/api/v3/broadcast_messages_spec.rb @@ -11,21 +11,21 @@ describe API::V3::BroadcastMessages do delete v3_api("/broadcast_messages/#{message.id}"), attributes_for(:broadcast_message) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 403 for users' do delete v3_api("/broadcast_messages/#{message.id}", user), attributes_for(:broadcast_message) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'deletes the broadcast message for admins' do expect do delete v3_api("/broadcast_messages/#{message.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { BroadcastMessage.count }.by(-1) end end diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb index 0a2ff1058e3..3f58b7ef384 100644 --- a/spec/requests/api/v3/builds_spec.rb +++ b/spec/requests/api/v3/builds_spec.rb @@ -21,7 +21,7 @@ describe API::V3::Builds do context 'authorized user' do it 'returns project builds' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array end @@ -44,7 +44,7 @@ describe API::V3::Builds do let(:query) { 'scope=pending' } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -54,7 +54,7 @@ describe API::V3::Builds do let(:json_build) { json_response.first } it 'return builds with status skipped' do - expect(response).to have_http_status 200 + expect(response).to have_gitlab_http_status 200 expect(json_response).to be_an Array expect(json_response.length).to eq 1 expect(json_build['status']).to eq 'skipped' @@ -65,7 +65,7 @@ describe API::V3::Builds do let(:query) { 'scope[0]=pending&scope[1]=running' } it do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array end end @@ -73,7 +73,7 @@ describe API::V3::Builds do context 'respond 400 when scope contains invalid state' do let(:query) { 'scope[0]=pending&scope[1]=unknown_status' } - it { expect(response).to have_http_status(400) } + it { expect(response).to have_gitlab_http_status(400) } end end @@ -81,7 +81,7 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not return project builds' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -93,7 +93,7 @@ describe API::V3::Builds do end it 'responds with 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -109,7 +109,7 @@ describe API::V3::Builds do end it 'returns project jobs for specific commit' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq 2 @@ -132,7 +132,7 @@ describe API::V3::Builds do end it 'returns an empty array' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response).to be_empty end @@ -148,7 +148,7 @@ describe API::V3::Builds do end it 'does not return project jobs' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(json_response.except('message')).to be_empty end end @@ -162,7 +162,7 @@ describe API::V3::Builds do context 'authorized user' do it 'returns specific job data' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('test') end @@ -180,7 +180,7 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not return specific job data' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -200,7 +200,7 @@ describe API::V3::Builds do end it 'returns specific job artifacts' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.headers).to include(download_headers) expect(response.body).to match_file(build.artifacts_file.file.file) end @@ -210,13 +210,13 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not return specific job artifacts' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end it 'does not return job artifacts if not uploaded' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -240,7 +240,7 @@ describe API::V3::Builds do end it 'gives 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -252,13 +252,13 @@ describe API::V3::Builds do end it 'gives 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'non-existing job' do shared_examples 'not found' do - it { expect(response).to have_http_status(:not_found) } + it { expect(response).to have_gitlab_http_status(:not_found) } end context 'has no such ref' do @@ -286,7 +286,7 @@ describe API::V3::Builds do "attachment; filename=#{build.artifacts_file.filename}" } end - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } it { expect(response.headers).to include(download_headers) } end @@ -327,7 +327,7 @@ describe API::V3::Builds do context 'authorized user' do it 'returns specific job trace' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.body).to eq(build.trace.raw) end end @@ -336,7 +336,7 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not return specific job trace' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -349,7 +349,7 @@ describe API::V3::Builds do context 'authorized user' do context 'user with :update_build persmission' do it 'cancels running or pending job' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(project.builds.first.status).to eq('canceled') end end @@ -358,7 +358,7 @@ describe API::V3::Builds do let(:api_user) { reporter.user } it 'does not cancel job' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -367,7 +367,7 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not cancel job' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -382,7 +382,7 @@ describe API::V3::Builds do context 'authorized user' do context 'user with :update_build permission' do it 'retries non-running job' do - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(project.builds.first.status).to eq('canceled') expect(json_response['status']).to eq('pending') end @@ -392,7 +392,7 @@ describe API::V3::Builds do let(:api_user) { reporter.user } it 'does not retry job' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -401,7 +401,7 @@ describe API::V3::Builds do let(:api_user) { nil } it 'does not retry job' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -471,7 +471,7 @@ describe API::V3::Builds do let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) } it 'plays the job' do - expect(response).to have_http_status 200 + expect(response).to have_gitlab_http_status 200 expect(json_response['user']['id']).to eq(user.id) expect(json_response['id']).to eq(build.id) end @@ -479,7 +479,7 @@ describe API::V3::Builds do context 'on a non-playable job' do it 'returns a status code 400, Bad Request' do - expect(response).to have_http_status 400 + expect(response).to have_gitlab_http_status 400 expect(response.body).to match("Unplayable Job") end end diff --git a/spec/requests/api/v3/commits_spec.rb b/spec/requests/api/v3/commits_spec.rb index 6d0ca33a6fa..d31c94ddd2c 100644 --- a/spec/requests/api/v3/commits_spec.rb +++ b/spec/requests/api/v3/commits_spec.rb @@ -19,7 +19,7 @@ describe API::V3::Commits do commit = project.repository.commit get v3_api("/projects/#{project.id}/repository/commits", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(commit.id) expect(json_response.first['committer_name']).to eq(commit.committer_name) @@ -30,7 +30,7 @@ describe API::V3::Commits do context "unauthorized user" do it "does not return project commits" do get v3_api("/projects/#{project.id}/repository/commits") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -69,7 +69,7 @@ describe API::V3::Commits do it "returns an invalid parameter error message" do get v3_api("/projects/#{project.id}/repository/commits?since=invalid-date", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('since is invalid') end end @@ -92,13 +92,13 @@ describe API::V3::Commits do it 'returns a 403 unauthorized for user without permissions' do post v3_api(url, user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'returns a 400 bad request if no params are given' do post v3_api(url, user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end describe 'create' do @@ -133,7 +133,7 @@ describe API::V3::Commits do it 'a new file in project repo' do post v3_api(url, user), valid_c_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) expect(json_response['committer_name']).to eq(user.name) expect(json_response['committer_email']).to eq(user.email) @@ -142,7 +142,7 @@ describe API::V3::Commits do it 'returns a 400 bad request if file exists' do post v3_api(url, user), invalid_c_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'with project path containing a dot in URL' do @@ -152,7 +152,7 @@ describe API::V3::Commits do it 'a new file in project repo' do post v3_api(url, user), valid_c_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end end @@ -187,14 +187,14 @@ describe API::V3::Commits do it 'an existing file in project repo' do post v3_api(url, user), valid_d_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post v3_api(url, user), invalid_d_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -232,14 +232,14 @@ describe API::V3::Commits do it 'an existing file in project repo' do post v3_api(url, user), valid_m_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post v3_api(url, user), invalid_m_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -275,14 +275,14 @@ describe API::V3::Commits do it 'an existing file in project repo' do post v3_api(url, user), valid_u_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'returns a 400 bad request if file does not exist' do post v3_api(url, user), invalid_u_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -348,14 +348,14 @@ describe API::V3::Commits do it 'are commited as one in project repo' do post v3_api(url, user), valid_mo_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(message) end it 'return a 400 bad request if there are any issues' do post v3_api(url, user), invalid_mo_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -365,7 +365,7 @@ describe API::V3::Commits do it "returns a commit by sha" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(project.repository.commit.id) expect(json_response['title']).to eq(project.repository.commit.title) expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions) @@ -375,13 +375,13 @@ describe API::V3::Commits do it "returns a 404 error if not found" do get v3_api("/projects/#{project.id}/repository/commits/invalid_sha", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns nil for commit without CI" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to be_nil end @@ -391,7 +391,7 @@ describe API::V3::Commits do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq(pipeline.status) end @@ -400,7 +400,7 @@ describe API::V3::Commits do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq("created") end end @@ -408,7 +408,7 @@ describe API::V3::Commits do context "unauthorized user" do it "does not return the selected commit" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -419,7 +419,7 @@ describe API::V3::Commits do it "returns the diff of the selected commit" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to be >= 1 @@ -428,14 +428,14 @@ describe API::V3::Commits do it "returns a 404 error if invalid commit" do get v3_api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context "unauthorized user" do it "does not return the diff of the selected commit" do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -444,7 +444,7 @@ describe API::V3::Commits do context 'authorized user' do it 'returns merge_request comments' do get v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq('a comment on a commit') @@ -453,14 +453,14 @@ describe API::V3::Commits do it 'returns a 404 error if merge_request_id not found' do get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context 'unauthorized user' do it 'does not return the diff of the selected commit' do get v3_api("/projects/#{project.id}/repository/commits/1234ab/comments") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -472,7 +472,7 @@ describe API::V3::Commits do it 'cherry picks a commit' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(master_pickable_commit.title) expect(json_response['message']).to eq(master_pickable_commit.cherry_pick_message(user)) expect(json_response['author_name']).to eq(master_pickable_commit.author_name) @@ -482,7 +482,7 @@ describe API::V3::Commits do it 'returns 400 if commit is already included in the target branch' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to include('Sorry, we cannot cherry-pick this commit automatically.') end @@ -492,35 +492,35 @@ describe API::V3::Commits do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('You are not allowed to push into this branch') end it 'returns 400 for missing parameters' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('branch is missing') end it 'returns 404 if commit is not found' do post v3_api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Commit Not Found') end it 'returns 404 if branch is not found' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Branch Not Found') end it 'returns 400 for missing parameters' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('branch is missing') end end @@ -529,7 +529,7 @@ describe API::V3::Commits do it 'does not cherry pick the commit' do post v3_api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -538,7 +538,7 @@ describe API::V3::Commits do context 'authorized user' do it 'returns comment' do post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to be_nil expect(json_response['line']).to be_nil @@ -548,7 +548,7 @@ describe API::V3::Commits do it 'returns the inline comment' do post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) expect(json_response['line']).to eq(1) @@ -557,19 +557,19 @@ describe API::V3::Commits do it 'returns 400 if note is missing' do post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 404 if note is attached to non existent commit' do post v3_api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context 'unauthorized user' do it 'does not return the diff of the selected commit' do post v3_api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/v3/deploy_keys_spec.rb b/spec/requests/api/v3/deploy_keys_spec.rb index 2affd0cfa51..785bc1eb4ba 100644 --- a/spec/requests/api/v3/deploy_keys_spec.rb +++ b/spec/requests/api/v3/deploy_keys_spec.rb @@ -46,7 +46,7 @@ describe API::V3::DeployKeys do it 'should return array of ssh keys' do get v3_api("/projects/#{project.id}/#{path}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(deploy_key.title) end @@ -56,14 +56,14 @@ describe API::V3::DeployKeys do it 'should return a single key' do get v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(deploy_key.title) end it 'should return 404 Not Found with invalid ID' do get v3_api("/projects/#{project.id}/#{path}/404", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -71,14 +71,14 @@ describe API::V3::DeployKeys do it 'should not create an invalid ssh key' do post v3_api("/projects/#{project.id}/#{path}", admin), { title: 'invalid key' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('key is missing') end it 'should not create a key without title' do post v3_api("/projects/#{project.id}/#{path}", admin), key: 'some key' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('title is missing') end @@ -95,7 +95,7 @@ describe API::V3::DeployKeys do post v3_api("/projects/#{project.id}/#{path}", admin), { key: deploy_key.key, title: deploy_key.title } end.not_to change { project.deploy_keys.count } - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'joins an existing ssh key to a new project' do @@ -103,7 +103,7 @@ describe API::V3::DeployKeys do post v3_api("/projects/#{project2.id}/#{path}", admin), { key: deploy_key.key, title: deploy_key.title } end.to change { project2.deploy_keys.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'accepts can_push parameter' do @@ -111,7 +111,7 @@ describe API::V3::DeployKeys do post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['can_push']).to eq(true) end end @@ -128,7 +128,7 @@ describe API::V3::DeployKeys do it 'should return 404 Not Found with invalid ID' do delete v3_api("/projects/#{project.id}/#{path}/404", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -141,7 +141,7 @@ describe API::V3::DeployKeys do post v3_api("/projects/#{project2.id}/#{path}/#{deploy_key.id}/enable", admin) end.to change { project2.deploy_keys.count }.from(0).to(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['id']).to eq(deploy_key.id) end end @@ -150,7 +150,7 @@ describe API::V3::DeployKeys do it 'should return a 404 error' do post v3_api("/projects/#{project2.id}/#{path}/#{deploy_key.id}/enable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -162,7 +162,7 @@ describe API::V3::DeployKeys do delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}/disable", admin) end.to change { project.deploy_keys.count }.from(1).to(0) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(deploy_key.id) end end @@ -171,7 +171,7 @@ describe API::V3::DeployKeys do it 'should return a 404 error' do delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}/disable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/deployments_spec.rb b/spec/requests/api/v3/deployments_spec.rb index 0389a264781..90eabda4dac 100644 --- a/spec/requests/api/v3/deployments_spec.rb +++ b/spec/requests/api/v3/deployments_spec.rb @@ -30,7 +30,7 @@ describe API::V3::Deployments do it 'returns projects deployments' do get v3_api("/projects/#{project.id}/deployments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['iid']).to eq(deployment.iid) @@ -42,7 +42,7 @@ describe API::V3::Deployments do it 'returns a 404 status code' do get v3_api("/projects/#{project.id}/deployments", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -52,7 +52,7 @@ describe API::V3::Deployments do it 'returns the projects deployment' do get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['sha']).to match /\A\h{40}\z/ expect(json_response['id']).to eq(deployment.id) end @@ -62,7 +62,7 @@ describe API::V3::Deployments do it 'returns a 404 status code' do get v3_api("/projects/#{project.id}/deployments/#{deployment.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/environments_spec.rb b/spec/requests/api/v3/environments_spec.rb index 39264e819a3..937250b5219 100644 --- a/spec/requests/api/v3/environments_spec.rb +++ b/spec/requests/api/v3/environments_spec.rb @@ -36,7 +36,7 @@ describe API::V3::Environments do it 'returns project environments' do get v3_api("/projects/#{project.id}/environments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['name']).to eq(environment.name) @@ -50,7 +50,7 @@ describe API::V3::Environments do it 'returns a 404 status code' do get v3_api("/projects/#{project.id}/environments", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -60,7 +60,7 @@ describe API::V3::Environments do it 'creates a environment with valid params' do post v3_api("/projects/#{project.id}/environments", user), name: "mepmep" - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['name']).to eq('mepmep') expect(json_response['slug']).to eq('mepmep') expect(json_response['external']).to be nil @@ -69,19 +69,19 @@ describe API::V3::Environments do it 'requires name to be passed' do post v3_api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if environment already exists' do post v3_api("/projects/#{project.id}/environments", user), name: environment.name - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 if slug is specified' do post v3_api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo" - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") end end @@ -90,7 +90,7 @@ describe API::V3::Environments do it 'rejects the request' do post v3_api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 400 when the required params are missing' do @@ -105,7 +105,7 @@ describe API::V3::Environments do put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), name: 'Mepmep', external_url: url - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') expect(json_response['external_url']).to eq(url) end @@ -115,7 +115,7 @@ describe API::V3::Environments do api_url = v3_api("/projects/#{project.id}/environments/#{environment.id}", user) put api_url, slug: slug + "-foo" - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed") end @@ -124,7 +124,7 @@ describe API::V3::Environments do put v3_api("/projects/#{project.id}/environments/#{environment.id}", user), name: 'Mepmep' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq('Mepmep') expect(json_response['external_url']).to eq(url) end @@ -132,7 +132,7 @@ describe API::V3::Environments do it 'returns a 404 if the environment does not exist' do put v3_api("/projects/#{project.id}/environments/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -141,13 +141,13 @@ describe API::V3::Environments do it 'returns a 200 for an existing environment' do delete v3_api("/projects/#{project.id}/environments/#{environment.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns a 404 for non existing id' do delete v3_api("/projects/#{project.id}/environments/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end end @@ -156,7 +156,7 @@ describe API::V3::Environments do it 'rejects the request' do delete v3_api("/projects/#{project.id}/environments/#{environment.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb index dc7f0eefd16..5500c1cf770 100644 --- a/spec/requests/api/v3/files_spec.rb +++ b/spec/requests/api/v3/files_spec.rb @@ -36,7 +36,7 @@ describe API::V3::Files do it "returns file info" do get v3_api(route, current_user), params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq('popen.rb') expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') @@ -112,7 +112,7 @@ describe API::V3::Files do it "creates a new file in project repo" do post v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -122,7 +122,7 @@ describe API::V3::Files do it "returns a 400 bad request if no params given" do post v3_api("/projects/#{project.id}/repository/files", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 if editor fails to create file" do @@ -131,7 +131,7 @@ describe API::V3::Files do post v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -140,7 +140,7 @@ describe API::V3::Files do post v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(author_email) expect(last_commit.author_name).to eq(author_name) @@ -153,7 +153,7 @@ describe API::V3::Files do it "creates a new file in project repo" do post v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['file_path']).to eq('newfile.rb') last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -175,7 +175,7 @@ describe API::V3::Files do it "updates existing file in project repo" do put v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(file_path) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -185,7 +185,7 @@ describe API::V3::Files do it "returns a 400 bad request if no params given" do put v3_api("/projects/#{project.id}/repository/files", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -194,7 +194,7 @@ describe API::V3::Files do put v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(author_email) expect(last_commit.author_name).to eq(author_name) @@ -214,7 +214,7 @@ describe API::V3::Files do it "deletes existing file in project repo" do delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(file_path) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(user.email) @@ -224,7 +224,7 @@ describe API::V3::Files do it "returns a 400 bad request if no params given" do delete v3_api("/projects/#{project.id}/repository/files", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 if fails to delete file" do @@ -232,7 +232,7 @@ describe API::V3::Files do delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context "when specifying an author" do @@ -241,7 +241,7 @@ describe API::V3::Files do delete v3_api("/projects/#{project.id}/repository/files", user), valid_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) last_commit = project.repository.commit.raw expect(last_commit.author_email).to eq(author_email) expect(last_commit.author_name).to eq(author_name) @@ -274,7 +274,7 @@ describe API::V3::Files do it "remains unchanged" do get v3_api("/projects/#{project.id}/repository/files", user), get_params - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['file_path']).to eq(file_path) expect(json_response['file_name']).to eq(file_path) expect(json_response['content']).to eq(put_params[:content]) diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 778fcc73c30..498cb42fad1 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -23,7 +23,7 @@ describe API::V3::Groups do it "returns authentication error" do get v3_api("/groups") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -31,7 +31,7 @@ describe API::V3::Groups do it "normal user: returns an array of groups of user1" do get v3_api("/groups", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response) @@ -41,7 +41,7 @@ describe API::V3::Groups do it "does not include statistics" do get v3_api("/groups", user1), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first).not_to include 'statistics' end @@ -51,7 +51,7 @@ describe API::V3::Groups do it "admin: returns an array of all groups" do get v3_api("/groups", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -59,7 +59,7 @@ describe API::V3::Groups do it "does not include statistics by default" do get v3_api("/groups", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first).not_to include('statistics') end @@ -76,7 +76,7 @@ describe API::V3::Groups do get v3_api("/groups", admin), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response) .to satisfy_one { |group| group['statistics'] == attributes } @@ -87,7 +87,7 @@ describe API::V3::Groups do it "returns all groups excluding skipped groups" do get v3_api("/groups", admin), skip_groups: [group2.id] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -101,7 +101,7 @@ describe API::V3::Groups do get v3_api("/groups", user1), all_available: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_groups).to contain_exactly(public_group.name, group1.name) end @@ -118,7 +118,7 @@ describe API::V3::Groups do it "sorts by name ascending by default" do get v3_api("/groups", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_groups).to eq([group3.name, group1.name]) end @@ -126,7 +126,7 @@ describe API::V3::Groups do it "sorts in descending order when passed" do get v3_api("/groups", user1), sort: "desc" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_groups).to eq([group1.name, group3.name]) end @@ -134,7 +134,7 @@ describe API::V3::Groups do it "sorts by the order_by param" do get v3_api("/groups", user1), order_by: "path" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_groups).to eq([group1.name, group3.name]) end @@ -146,7 +146,7 @@ describe API::V3::Groups do it 'returns authentication error' do get v3_api('/groups/owned') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -154,7 +154,7 @@ describe API::V3::Groups do it 'returns an array of groups the user owns' do get v3_api('/groups/owned', user2) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['name']).to eq(group2.name) @@ -170,7 +170,7 @@ describe API::V3::Groups do get v3_api("/groups/#{group1.id}", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(group1.id) expect(json_response['name']).to eq(group1.name) expect(json_response['path']).to eq(group1.path) @@ -192,13 +192,13 @@ describe API::V3::Groups do it "does not return a non existing group" do get v3_api("/groups/1328", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not return a group not attached to user1" do get v3_api("/groups/#{group2.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -206,14 +206,14 @@ describe API::V3::Groups do it "returns any existing group" do get v3_api("/groups/#{group2.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(group2.name) end it "does not return a non existing group" do get v3_api("/groups/1328", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -221,20 +221,20 @@ describe API::V3::Groups do it 'returns any existing group' do get v3_api("/groups/#{group1.path}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(group1.name) end it 'does not return a non existing group' do get v3_api('/groups/unknown', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not return a group not attached to user1' do get v3_api("/groups/#{group2.path}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -246,7 +246,7 @@ describe API::V3::Groups do it 'updates the group' do put v3_api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) expect(json_response['request_access_enabled']).to eq(true) end @@ -254,7 +254,7 @@ describe API::V3::Groups do it 'returns 404 for a non existing group' do put v3_api('/groups/1328', user1), name: new_group_name - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -262,7 +262,7 @@ describe API::V3::Groups do it 'updates the group' do put v3_api("/groups/#{group1.id}", admin), name: new_group_name - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(new_group_name) end end @@ -271,7 +271,7 @@ describe API::V3::Groups do it 'does not updates the group' do put v3_api("/groups/#{group1.id}", user2), name: new_group_name - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -279,7 +279,7 @@ describe API::V3::Groups do it 'returns 404 when trying to update the group' do put v3_api("/groups/#{group2.id}", user1), name: new_group_name - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -289,7 +289,7 @@ describe API::V3::Groups do it "returns the group's projects" do get v3_api("/groups/#{group1.id}/projects", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) @@ -299,7 +299,7 @@ describe API::V3::Groups do it "returns the group's projects with simple representation" do get v3_api("/groups/#{group1.id}/projects", user1), simple: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) @@ -311,7 +311,7 @@ describe API::V3::Groups do get v3_api("/groups/#{group1.id}/projects", user1), visibility: 'public' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an(Array) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(public_project.name) @@ -320,13 +320,13 @@ describe API::V3::Groups do it "does not return a non existing group" do get v3_api("/groups/1328/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not return a group not attached to user1" do get v3_api("/groups/#{group2.id}/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "only returns projects to which user has access" do @@ -334,7 +334,7 @@ describe API::V3::Groups do get v3_api("/groups/#{group1.id}/projects", user3) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project3.name) end @@ -344,7 +344,7 @@ describe API::V3::Groups do get v3_api("/groups/#{project2.group.id}/projects", user3), owned: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) end @@ -354,7 +354,7 @@ describe API::V3::Groups do get v3_api("/groups/#{group1.id}/projects", user1), starred: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project1.name) end @@ -364,7 +364,7 @@ describe API::V3::Groups do it "returns any existing group" do get v3_api("/groups/#{group2.id}/projects", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(project2.name) end @@ -372,7 +372,7 @@ describe API::V3::Groups do it "does not return a non existing group" do get v3_api("/groups/1328/projects", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -380,7 +380,7 @@ describe API::V3::Groups do it 'returns any existing group' do get v3_api("/groups/#{group1.path}/projects", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_names = json_response.map { |proj| proj['name'] } expect(project_names).to match_array([project1.name, project3.name]) end @@ -388,13 +388,13 @@ describe API::V3::Groups do it 'does not return a non existing group' do get v3_api('/groups/unknown/projects', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not return a group not attached to user1' do get v3_api("/groups/#{group2.path}/projects", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -404,7 +404,7 @@ describe API::V3::Groups do it "does not create group" do post v3_api("/groups", user1), attributes_for(:group) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -414,7 +414,7 @@ describe API::V3::Groups do post v3_api("/groups", user3), group - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(group[:name]) expect(json_response["path"]).to eq(group[:path]) @@ -428,7 +428,7 @@ describe API::V3::Groups do post v3_api("/groups", user3), group - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["full_path"]).to eq("#{parent.path}/#{group[:path]}") expect(json_response["parent_id"]).to eq(parent.id) @@ -437,20 +437,20 @@ describe API::V3::Groups do it "does not create group, duplicate" do post v3_api("/groups", user3), { name: 'Duplicate Test', path: group2.path } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(response.message).to eq("Bad Request") end it "returns 400 bad request error if name not given" do post v3_api("/groups", user3), { path: group2.path } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 400 bad request error if path not given" do post v3_api("/groups", user3), { name: 'test' } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end @@ -460,7 +460,7 @@ describe API::V3::Groups do it "removes group" do delete v3_api("/groups/#{group1.id}", user1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "does not remove a group if not an owner" do @@ -469,19 +469,19 @@ describe API::V3::Groups do delete v3_api("/groups/#{group1.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "does not remove a non existing group" do delete v3_api("/groups/1328", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "does not remove a group not attached to user1" do delete v3_api("/groups/#{group2.id}", user1) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -489,13 +489,13 @@ describe API::V3::Groups do it "removes any existing group" do delete v3_api("/groups/#{group2.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "does not remove a non existing group" do delete v3_api("/groups/1328", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -513,7 +513,7 @@ describe API::V3::Groups do it "does not transfer project to group" do post v3_api("/groups/#{group1.id}/projects/#{project.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -521,7 +521,7 @@ describe API::V3::Groups do it "transfers project to group" do post v3_api("/groups/#{group1.id}/projects/#{project.id}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when using project path in URL' do @@ -529,7 +529,7 @@ describe API::V3::Groups do it "transfers project to group" do post v3_api("/groups/#{group1.id}/projects/#{project_path}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -537,7 +537,7 @@ describe API::V3::Groups do it "does not transfer project to group" do post v3_api("/groups/#{group1.id}/projects/nogroup%2Fnoproject", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -547,7 +547,7 @@ describe API::V3::Groups do it "transfers project to group" do post v3_api("/groups/#{group1.path}/projects/#{project_path}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end @@ -555,7 +555,7 @@ describe API::V3::Groups do it "does not transfer project to group" do post v3_api("/groups/noexist/projects/#{project_path}", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb index 86768d7397a..39a47a62f16 100644 --- a/spec/requests/api/v3/issues_spec.rb +++ b/spec/requests/api/v3/issues_spec.rb @@ -59,7 +59,7 @@ describe API::V3::Issues, :mailer do it "returns authentication error" do get v3_api("/issues") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -67,7 +67,7 @@ describe API::V3::Issues, :mailer do it "returns an array of issues" do get v3_api("/issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(issue.title) expect(json_response.last).to have_key('web_url') @@ -76,7 +76,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of closed issues' do get v3_api('/issues?state=closed', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -85,7 +85,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of opened issues' do get v3_api('/issues?state=opened', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -94,7 +94,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of all issues' do get v3_api('/issues?state=all', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -104,7 +104,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled issues' do get v3_api("/issues?labels=#{label.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -113,7 +113,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled issues when at least one label matches' do get v3_api("/issues?labels=#{label.title},foo,bar", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -122,7 +122,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no issue matches labels' do get v3_api('/issues?labels=foo,bar', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -130,7 +130,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled issues matching given state' do get v3_api("/issues?labels=#{label.title}&state=opened", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -140,7 +140,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no issue matches labels and state filters' do get v3_api("/issues?labels=#{label.title}&state=closed", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -148,7 +148,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no issue matches milestone' do get v3_api("/issues?milestone=#{empty_milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -156,7 +156,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if milestone does not exist' do get v3_api("/issues?milestone=foo", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -164,7 +164,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues in given milestone' do get v3_api("/issues?milestone=#{milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -175,7 +175,7 @@ describe API::V3::Issues, :mailer do get v3_api("/issues?milestone=#{milestone.title}", user), '&state=closed' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -184,7 +184,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues with no milestone' do get v3_api("/issues?milestone=#{no_milestone_title}", author) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(confidential_issue.id) @@ -195,7 +195,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -205,7 +205,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -215,7 +215,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -225,7 +225,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -233,7 +233,7 @@ describe API::V3::Issues, :mailer do it 'matches V3 response schema' do get v3_api('/issues', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v3/issues') end end @@ -285,7 +285,7 @@ describe API::V3::Issues, :mailer do it 'returns all group issues (including opened and closed)' do get v3_api(base_url, admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) end @@ -293,7 +293,7 @@ describe API::V3::Issues, :mailer do it 'returns group issues without confidential issues for non project members' do get v3_api("#{base_url}?state=opened", non_member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(group_issue.title) @@ -302,7 +302,7 @@ describe API::V3::Issues, :mailer do it 'returns group confidential issues for author' do get v3_api("#{base_url}?state=opened", author) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -310,7 +310,7 @@ describe API::V3::Issues, :mailer do it 'returns group confidential issues for assignee' do get v3_api("#{base_url}?state=opened", assignee) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -318,7 +318,7 @@ describe API::V3::Issues, :mailer do it 'returns group issues with confidential issues for project members' do get v3_api("#{base_url}?state=opened", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -326,7 +326,7 @@ describe API::V3::Issues, :mailer do it 'returns group confidential issues for admin' do get v3_api("#{base_url}?state=opened", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -334,7 +334,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled group issues' do get v3_api("#{base_url}?labels=#{group_label.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([group_label.title]) @@ -343,7 +343,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled group issues where all labels match' do get v3_api("#{base_url}?labels=#{group_label.title},foo,bar", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -351,7 +351,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no group issue matches labels' do get v3_api("#{base_url}?labels=foo,bar", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -359,7 +359,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no issue matches milestone' do get v3_api("#{base_url}?milestone=#{group_empty_milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -367,7 +367,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if milestone does not exist' do get v3_api("#{base_url}?milestone=foo", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -375,7 +375,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues in given milestone' do get v3_api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_issue.id) @@ -385,7 +385,7 @@ describe API::V3::Issues, :mailer do get v3_api("#{base_url}?milestone=#{group_milestone.title}", user), '&state=closed' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_closed_issue.id) @@ -394,7 +394,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues with no milestone' do get v3_api("#{base_url}?milestone=#{no_milestone_title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_confidential_issue.id) @@ -405,7 +405,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -415,7 +415,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -425,7 +425,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -435,7 +435,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -447,7 +447,7 @@ describe API::V3::Issues, :mailer do it 'returns 404 when project does not exist' do get v3_api('/projects/1000/issues', non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 on private projects for other users" do @@ -456,7 +456,7 @@ describe API::V3::Issues, :mailer do get v3_api("/projects/#{private_project.id}/issues", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns no issues when user has access to project but not issues' do @@ -471,7 +471,7 @@ describe API::V3::Issues, :mailer do it 'returns project issues without confidential issues for non project members' do get v3_api("#{base_url}/issues", non_member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -480,7 +480,7 @@ describe API::V3::Issues, :mailer do it 'returns project issues without confidential issues for project members with guest role' do get v3_api("#{base_url}/issues", guest) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq(issue.title) @@ -489,7 +489,7 @@ describe API::V3::Issues, :mailer do it 'returns project confidential issues for author' do get v3_api("#{base_url}/issues", author) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -498,7 +498,7 @@ describe API::V3::Issues, :mailer do it 'returns project confidential issues for assignee' do get v3_api("#{base_url}/issues", assignee) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -507,7 +507,7 @@ describe API::V3::Issues, :mailer do it 'returns project issues with confidential issues for project members' do get v3_api("#{base_url}/issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -516,7 +516,7 @@ describe API::V3::Issues, :mailer do it 'returns project confidential issues for admin' do get v3_api("#{base_url}/issues", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.first['title']).to eq(issue.title) @@ -525,7 +525,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled project issues' do get v3_api("#{base_url}/issues?labels=#{label.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -534,7 +534,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of labeled project issues where all labels match' do get v3_api("#{base_url}/issues?labels=#{label.title},foo,bar", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label.title]) @@ -543,7 +543,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no project issue matches labels' do get v3_api("#{base_url}/issues?labels=foo,bar", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -551,7 +551,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if no issue matches milestone' do get v3_api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -559,7 +559,7 @@ describe API::V3::Issues, :mailer do it 'returns an empty array if milestone does not exist' do get v3_api("#{base_url}/issues?milestone=foo", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -567,7 +567,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues in given milestone' do get v3_api("#{base_url}/issues?milestone=#{milestone.title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['id']).to eq(issue.id) @@ -578,7 +578,7 @@ describe API::V3::Issues, :mailer do get v3_api("#{base_url}/issues?milestone=#{milestone.title}", user), '&state=closed' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) @@ -587,7 +587,7 @@ describe API::V3::Issues, :mailer do it 'returns an array of issues with no milestone' do get v3_api("#{base_url}/issues?milestone=#{no_milestone_title}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(confidential_issue.id) @@ -598,7 +598,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -608,7 +608,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -618,7 +618,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort.reverse) end @@ -628,7 +628,7 @@ describe API::V3::Issues, :mailer do response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(response_dates).to eq(response_dates.sort) end @@ -638,7 +638,7 @@ describe API::V3::Issues, :mailer do it 'exposes known attributes' do get v3_api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(issue.id) expect(json_response['iid']).to eq(issue.iid) expect(json_response['project_id']).to eq(issue.project.id) @@ -657,7 +657,7 @@ describe API::V3::Issues, :mailer do it "returns a project issue by id" do get v3_api("/projects/#{project.id}/issues/#{issue.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) end @@ -682,26 +682,26 @@ describe API::V3::Issues, :mailer do it "returns 404 if issue id not found" do get v3_api("/projects/#{project.id}/issues/54321", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'confidential issues' do it "returns 404 for non project members" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 for project members with guest role" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns confidential issue for project members" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -709,7 +709,7 @@ describe API::V3::Issues, :mailer do it "returns confidential issue for author" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", author) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -717,7 +717,7 @@ describe API::V3::Issues, :mailer do it "returns confidential issue for assignee" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -725,7 +725,7 @@ describe API::V3::Issues, :mailer do it "returns confidential issue for admin" do get v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['iid']).to eq(confidential_issue.iid) end @@ -737,7 +737,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', assignee_id: assignee.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['labels']).to eq(%w(label label2)) @@ -749,7 +749,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: true - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_truthy end @@ -758,7 +758,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: 'y' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_truthy end @@ -767,7 +767,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: false - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['confidential']).to be_falsy end @@ -776,7 +776,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', confidential: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') end @@ -795,7 +795,7 @@ describe API::V3::Issues, :mailer do it "returns a 400 bad request if title not given" do post v3_api("/projects/#{project.id}/issues", user), labels: 'label, label2' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'allows special label names' do @@ -815,7 +815,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'g' * 256 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -834,7 +834,7 @@ describe API::V3::Issues, :mailer do end it 'creates a new project issue' do - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) end it 'resolves the discussions in a merge request' do @@ -855,7 +855,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', due_date: due_date - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['due_date']).to eq(due_date) @@ -868,7 +868,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2', created_at: creation_time - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end @@ -899,7 +899,7 @@ describe API::V3::Issues, :mailer do it "does not create a new project issue" do expect { post v3_api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -917,7 +917,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -925,7 +925,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/44444", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'allows special label names' do @@ -946,21 +946,21 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member), title: 'updated title' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "returns 403 for project members with guest role" do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest), title: 'updated title' - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "updates a confidential issue for project members" do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -968,7 +968,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", author), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -976,7 +976,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -984,7 +984,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), confidential: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_truthy end @@ -992,7 +992,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), confidential: false - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['confidential']).to be_falsy end @@ -1000,7 +1000,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), confidential: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('confidential is invalid') end end @@ -1021,7 +1021,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) spam_logs = SpamLog.all @@ -1041,7 +1041,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to eq([label.title]) end @@ -1060,7 +1060,7 @@ describe API::V3::Issues, :mailer do it 'removes all labels' do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to eq([]) end @@ -1068,7 +1068,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'foo,bar' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'foo' expect(json_response['labels']).to include 'bar' end @@ -1092,7 +1092,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'g' * 256 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['title']).to eq([ 'is too long (maximum is 255 characters)' ]) @@ -1104,7 +1104,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label2', state_event: "close" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label2' expect(json_response['state']).to eq "closed" end @@ -1112,7 +1112,7 @@ describe API::V3::Issues, :mailer do it 'reopens a project isssue' do put v3_api("/projects/#{project.id}/issues/#{closed_issue.id}", user), state_event: 'reopen' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq 'opened' end @@ -1122,7 +1122,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label3', state_event: 'close', updated_at: update_time - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['labels']).to include 'label3' expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end @@ -1135,7 +1135,7 @@ describe API::V3::Issues, :mailer do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to eq(due_date) end end @@ -1144,14 +1144,14 @@ describe API::V3::Issues, :mailer do it 'updates an issue with no assignee' do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), assignee_id: 0 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignee']).to eq(nil) end it 'updates an issue with assignee' do put v3_api("/projects/#{project.id}/issues/#{issue.id}", user), assignee_id: user2.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['assignee']['name']).to eq(user2.name) end end @@ -1160,13 +1160,13 @@ describe API::V3::Issues, :mailer do it "rejects a non member from deleting an issue" do delete v3_api("/projects/#{project.id}/issues/#{issue.id}", non_member) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it "rejects a developer from deleting an issue" do delete v3_api("/projects/#{project.id}/issues/#{issue.id}", author) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end context "when the user is project owner" do @@ -1176,7 +1176,7 @@ describe API::V3::Issues, :mailer do it "deletes the issue if an admin requests it" do delete v3_api("/projects/#{project.id}/issues/#{issue.id}", owner) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq 'opened' end end @@ -1185,7 +1185,7 @@ describe API::V3::Issues, :mailer do it 'returns 404 when trying to move an issue' do delete v3_api("/projects/#{project.id}/issues/123", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1198,7 +1198,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project.id) end @@ -1207,7 +1207,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: project.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue to project it originates from!') end end @@ -1217,7 +1217,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: target_project2.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!') end end @@ -1226,7 +1226,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", admin), to_project_id: target_project2.id - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['project_id']).to eq(target_project2.id) end @@ -1235,7 +1235,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/123/move", user), to_project_id: target_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Issue Not Found') end end @@ -1245,7 +1245,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/123/issues/#{issue.id}/move", user), to_project_id: target_project.id - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end end @@ -1255,7 +1255,7 @@ describe API::V3::Issues, :mailer do post v3_api("/projects/#{project.id}/issues/#{issue.id}/move", user), to_project_id: 123 - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1264,26 +1264,26 @@ describe API::V3::Issues, :mailer do it 'subscribes to an issue' do post v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the issue is not found' do post v3_api("/projects/#{project.id}/issues/123/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if the issue is confidential' do post v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1291,26 +1291,26 @@ describe API::V3::Issues, :mailer do it 'unsubscribes from an issue' do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the issue is not found' do delete v3_api("/projects/#{project.id}/issues/123/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 404 if the issue is confidential' do delete v3_api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/requests/api/v3/labels_spec.rb b/spec/requests/api/v3/labels_spec.rb index 32f37a08024..1d31213d5ca 100644 --- a/spec/requests/api/v3/labels_spec.rb +++ b/spec/requests/api/v3/labels_spec.rb @@ -27,7 +27,7 @@ describe API::V3::Labels do get v3_api("/projects/#{project.id}/labels", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(3) expect(json_response.first.keys).to match_array expected_keys @@ -71,7 +71,7 @@ describe API::V3::Labels do it "subscribes to the label" do post v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -81,7 +81,7 @@ describe API::V3::Labels do it "subscribes to the label" do post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_truthy end @@ -93,7 +93,7 @@ describe API::V3::Labels do it "returns 304" do post v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end @@ -101,7 +101,7 @@ describe API::V3::Labels do it "returns 404 error" do post v3_api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -113,7 +113,7 @@ describe API::V3::Labels do it "unsubscribes from the label" do delete v3_api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -123,7 +123,7 @@ describe API::V3::Labels do it "unsubscribes from the label" do delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["name"]).to eq(label1.title) expect(json_response["subscribed"]).to be_falsey end @@ -135,7 +135,7 @@ describe API::V3::Labels do it "returns 304" do delete v3_api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end @@ -143,7 +143,7 @@ describe API::V3::Labels do it "returns 404 error" do delete v3_api("/projects/#{project.id}/labels/1234/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -152,18 +152,18 @@ describe API::V3::Labels do it 'returns 200 for existing label' do delete v3_api("/projects/#{project.id}/labels", user), name: 'label1' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns 404 for non existing label' do delete v3_api("/projects/#{project.id}/labels", user), name: 'label2' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Label Not Found') end it 'returns 400 for wrong parameters' do delete v3_api("/projects/#{project.id}/labels", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end end diff --git a/spec/requests/api/v3/members_spec.rb b/spec/requests/api/v3/members_spec.rb index bc918a8eb02..68be3d24c26 100644 --- a/spec/requests/api/v3/members_spec.rb +++ b/spec/requests/api/v3/members_spec.rb @@ -34,7 +34,7 @@ describe API::V3::Members do user = public_send(type) get v3_api("/#{source_type.pluralize}/#{source.id}/members", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] end @@ -46,7 +46,7 @@ describe API::V3::Members do get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] end @@ -54,7 +54,7 @@ describe API::V3::Members do it 'finds members with query string' do get v3_api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.count).to eq(1) expect(json_response.first['username']).to eq(master.username) end @@ -74,7 +74,7 @@ describe API::V3::Members do user = public_send(type) get v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) # User attributes expect(json_response['id']).to eq(developer.id) expect(json_response['name']).to eq(developer.name) @@ -109,7 +109,7 @@ describe API::V3::Members do post v3_api("/#{source_type.pluralize}/#{source.id}/members", user), user_id: access_requester.id, access_level: Member::MASTER - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -122,7 +122,7 @@ describe API::V3::Members do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: access_requester.id, access_level: Member::MASTER - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) expect(source.requesters.count).to eq(0) expect(json_response['id']).to eq(access_requester.id) @@ -135,7 +135,7 @@ describe API::V3::Members do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end.to change { source.members.count }.by(1) expect(json_response['id']).to eq(stranger.id) expect(json_response['access_level']).to eq(Member::DEVELOPER) @@ -147,28 +147,28 @@ describe API::V3::Members do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: master.id, access_level: Member::MASTER - expect(response).to have_http_status(source_type == 'project' ? 201 : 409) + expect(response).to have_gitlab_http_status(source_type == 'project' ? 201 : 409) end it 'returns 400 when user_id is not given' do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), access_level: Member::MASTER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 400 when access_level is not given' do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 422 when access_level is not valid' do post v3_api("/#{source_type.pluralize}/#{source.id}/members", master), user_id: stranger.id, access_level: 1234 - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end end @@ -190,7 +190,7 @@ describe API::V3::Members do put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user), access_level: Member::MASTER - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -201,7 +201,7 @@ describe API::V3::Members do put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), access_level: Member::MASTER, expires_at: '2016-08-05' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(developer.id) expect(json_response['access_level']).to eq(Member::MASTER) expect(json_response['expires_at']).to eq('2016-08-05') @@ -212,20 +212,20 @@ describe API::V3::Members do put v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master), access_level: Member::MASTER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 400 when access_level is not given' do put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns 422 when access level is not valid' do put v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master), access_level: 1234 - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end end @@ -243,7 +243,7 @@ describe API::V3::Members do user = public_send(type) delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -254,7 +254,7 @@ describe API::V3::Members do expect do delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { source.members.count }.by(-1) end end @@ -265,7 +265,7 @@ describe API::V3::Members do expect do delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master) - expect(response).to have_http_status(source_type == 'project' ? 200 : 404) + expect(response).to have_gitlab_http_status(source_type == 'project' ? 200 : 404) end.not_to change { source.requesters.count } end end @@ -274,7 +274,7 @@ describe API::V3::Members do expect do delete v3_api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { source.members.count }.by(-1) end end @@ -282,7 +282,7 @@ describe API::V3::Members do it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do delete v3_api("/#{source_type.pluralize}/#{source.id}/members/123", master) - expect(response).to have_http_status(source_type == 'project' ? 200 : 404) + expect(response).to have_gitlab_http_status(source_type == 'project' ? 200 : 404) end end end @@ -333,7 +333,7 @@ describe API::V3::Members do post v3_api("/projects/#{project.id}/members", master), user_id: stranger.id, access_level: Member::OWNER - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end.to change { project.members.count }.by(0) end end diff --git a/spec/requests/api/v3/merge_request_diffs_spec.rb b/spec/requests/api/v3/merge_request_diffs_spec.rb index 3f21ff40726..e613036a88d 100644 --- a/spec/requests/api/v3/merge_request_diffs_spec.rb +++ b/spec/requests/api/v3/merge_request_diffs_spec.rb @@ -24,7 +24,7 @@ describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs' do it 'returns a 404 when merge_request_id not found' do get v3_api("/projects/#{project.id}/merge_requests/999/versions", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -42,7 +42,7 @@ describe API::V3::MergeRequestDiffs, 'MergeRequestDiffs' do it 'returns a 404 when merge_request_id not found' do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb index 86f38dd4ec1..26251b95680 100644 --- a/spec/requests/api/v3/merge_requests_spec.rb +++ b/spec/requests/api/v3/merge_requests_spec.rb @@ -1,6 +1,8 @@ require "spec_helper" describe API::MergeRequests do + include ProjectForksHelper + let(:base_time) { Time.now } let(:user) { create(:user) } let(:admin) { create(:user, :admin) } @@ -19,14 +21,14 @@ describe API::MergeRequests do context "when unauthenticated" do it "returns authentication error" do get v3_api("/projects/#{project.id}/merge_requests") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context "when authenticated" do it "returns an array of all merge_requests" do get v3_api("/projects/#{project.id}/merge_requests", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -42,7 +44,7 @@ describe API::MergeRequests do it "returns an array of all merge_requests" do get v3_api("/projects/#{project.id}/merge_requests?state", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) @@ -50,7 +52,7 @@ describe API::MergeRequests do it "returns an array of open merge_requests" do get v3_api("/projects/#{project.id}/merge_requests?state=opened", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.last['title']).to eq(merge_request.title) @@ -58,7 +60,7 @@ describe API::MergeRequests do it "returns an array of closed merge_requests" do get v3_api("/projects/#{project.id}/merge_requests?state=closed", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_closed.title) @@ -66,7 +68,7 @@ describe API::MergeRequests do it "returns an array of merged merge_requests" do get v3_api("/projects/#{project.id}/merge_requests?state=merged", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(merge_request_merged.title) @@ -75,7 +77,7 @@ describe API::MergeRequests do it 'matches V3 response schema' do get v3_api("/projects/#{project.id}/merge_requests", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v3/merge_requests') end @@ -87,7 +89,7 @@ describe API::MergeRequests do it "returns an array of merge_requests in ascending order" do get v3_api("/projects/#{project.id}/merge_requests?sort=asc", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map { |merge_request| merge_request['created_at'] } @@ -96,7 +98,7 @@ describe API::MergeRequests do it "returns an array of merge_requests in descending order" do get v3_api("/projects/#{project.id}/merge_requests?sort=desc", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map { |merge_request| merge_request['created_at'] } @@ -105,7 +107,7 @@ describe API::MergeRequests do it "returns an array of merge_requests ordered by updated_at" do get v3_api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map { |merge_request| merge_request['updated_at'] } @@ -114,7 +116,7 @@ describe API::MergeRequests do it "returns an array of merge_requests ordered by created_at" do get v3_api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(3) response_dates = json_response.map { |merge_request| merge_request['created_at'] } @@ -128,7 +130,7 @@ describe API::MergeRequests do it 'exposes known attributes' do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(merge_request.id) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['project_id']).to eq(merge_request.project.id) @@ -156,7 +158,7 @@ describe API::MergeRequests do it "returns merge_request" do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(merge_request.title) expect(json_response['iid']).to eq(merge_request.iid) expect(json_response['work_in_progress']).to eq(false) @@ -176,7 +178,7 @@ describe API::MergeRequests do it 'returns merge_request by iid array' do get v3_api("/projects/#{project.id}/merge_requests", user), iid: [merge_request.iid, merge_request_closed.iid] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['title']).to eq merge_request_closed.title @@ -185,7 +187,7 @@ describe API::MergeRequests do it "returns a 404 error if merge_request_id not found" do get v3_api("/projects/#{project.id}/merge_requests/999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'Work in Progress' do @@ -193,7 +195,7 @@ describe API::MergeRequests do it "returns merge_request" do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['work_in_progress']).to eq(true) end end @@ -212,7 +214,7 @@ describe API::MergeRequests do it 'returns a 404 when merge_request_id not found' do get v3_api("/projects/#{project.id}/merge_requests/999/commits", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -225,7 +227,7 @@ describe API::MergeRequests do it 'returns a 404 when merge_request_id not found' do get v3_api("/projects/#{project.id}/merge_requests/999/changes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -241,7 +243,7 @@ describe API::MergeRequests do milestone_id: milestone.id, remove_source_branch: true - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(%w(label label2)) expect(json_response['milestone']['id']).to eq(milestone.id) @@ -251,25 +253,25 @@ describe API::MergeRequests do it "returns 422 when source_branch equals target_branch" do post v3_api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "master", target_branch: "master", author: user - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end it "returns 400 when source_branch is missing" do post v3_api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", target_branch: "master", author: user - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 400 when target_branch is missing" do post v3_api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "markdown", author: user - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 400 when title is missing" do post v3_api("/projects/#{project.id}/merge_requests", user), target_branch: 'master', source_branch: 'markdown' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'allows special label names' do @@ -305,24 +307,24 @@ describe API::MergeRequests do target_branch: 'master', author: user end.to change { MergeRequest.count }.by(0) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) end end end context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:forked_project) { fork_project(project, user2) } let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before do - fork_project.add_reporter(user2) + forked_project.add_reporter(user2) allow_any_instance_of(MergeRequest).to receive(:write_ref) end it "returns merge_request" do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' expect(response).to have_gitlab_http_status(201) @@ -331,10 +333,10 @@ describe API::MergeRequests do end it "does not return 422 when source_branch equals target_branch" do - expect(project.id).not_to eq(fork_project.id) - expect(fork_project.forked?).to be_truthy - expect(fork_project.forked_from_project).to eq(project) - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + expect(project.id).not_to eq(forked_project.id) + expect(forked_project.forked?).to be_truthy + expect(forked_project.forked_from_project).to eq(project) + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') @@ -343,7 +345,7 @@ describe API::MergeRequests do it "returns 422 when target project has disabled merge requests" do project.project_feature.update(merge_requests_access_level: 0) - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test', target_branch: "master", source_branch: 'markdown', @@ -354,36 +356,26 @@ describe API::MergeRequests do end it "returns 400 when source_branch is missing" do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end it "returns 400 when target_branch is missing" do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end it "returns 400 when title is missing" do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id expect(response).to have_gitlab_http_status(400) end context 'when target_branch is specified' do - it 'returns 422 if not a forked project' do - post v3_api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', - target_branch: 'master', - source_branch: 'markdown', - author: user, - target_project_id: fork_project.id - expect(response).to have_gitlab_http_status(422) - end - it 'returns 422 if targeting a different fork' do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', @@ -394,8 +386,8 @@ describe API::MergeRequests do end it "returns 201 when target_branch is specified and for the same project" do - post v3_api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id + post v3_api("/projects/#{forked_project.id}/merge_requests", user2), + title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id expect(response).to have_gitlab_http_status(201) end end @@ -411,7 +403,7 @@ describe API::MergeRequests do it "denies the deletion of the merge request" do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", developer) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -419,7 +411,7 @@ describe API::MergeRequests do it "destroys the merge request owners can destroy" do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -430,7 +422,7 @@ describe API::MergeRequests do it "returns merge_request in case of success" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns 406 if branch can't be merged" do @@ -439,21 +431,21 @@ describe API::MergeRequests do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_http_status(406) + expect(response).to have_gitlab_http_status(406) expect(json_response['message']).to eq('Branch cannot be merged') end it "returns 405 if merge_request is not open" do merge_request.close put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_http_status(405) + expect(response).to have_gitlab_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end it "returns 405 if merge_request is a work in progress" do merge_request.update_attribute(:title, "WIP: #{merge_request.title}") put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_http_status(405) + expect(response).to have_gitlab_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -462,7 +454,7 @@ describe API::MergeRequests do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) - expect(response).to have_http_status(405) + expect(response).to have_gitlab_http_status(405) expect(json_response['message']).to eq('405 Method Not Allowed') end @@ -470,21 +462,21 @@ describe API::MergeRequests do user2 = create(:user) project.team << [user2, :reporter] put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(json_response['message']).to eq('401 Unauthorized') end it "returns 409 if the SHA parameter doesn't match" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha.reverse - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) expect(json_response['message']).to start_with('SHA does not match HEAD of source branch') end it "succeeds if the SHA parameter matches" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), sha: merge_request.diff_head_sha - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "enables merge when pipeline succeeds if the pipeline is active" do @@ -493,7 +485,7 @@ describe API::MergeRequests do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('Test') expect(json_response['merge_when_build_succeeds']).to eq(true) end @@ -504,39 +496,39 @@ describe API::MergeRequests do it "returns merge_request" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') end end it "updates title and returns merge_request" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('New title') end it "updates description and returns merge_request" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq('New description') end it "updates milestone_id and returns merge_request" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['milestone']['id']).to eq(milestone.id) end it "returns merge_request with renamed target_branch" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['target_branch']).to eq('wiki') end it "returns merge_request that removes the source branch" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), remove_source_branch: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['force_remove_source_branch']).to be_truthy end @@ -557,7 +549,7 @@ describe API::MergeRequests do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil merge_request.reload - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(merge_request.state).to eq('opened') end @@ -565,7 +557,7 @@ describe API::MergeRequests do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil merge_request.reload - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(merge_request.state).to eq('opened') end end @@ -576,7 +568,7 @@ describe API::MergeRequests do post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) @@ -585,13 +577,13 @@ describe API::MergeRequests do it "returns 400 if note is missing" do post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns 404 if note is attached to non existent merge request" do post v3_api("/projects/#{project.id}/merge_requests/404/comments", user), note: 'My comment' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -601,7 +593,7 @@ describe API::MergeRequests do it "returns merge_request comments ordered by created_at" do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq("a comment on a MR") @@ -611,7 +603,7 @@ describe API::MergeRequests do it "returns a 404 error if merge_request_id not found" do get v3_api("/projects/#{project.id}/merge_requests/999/comments", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -623,7 +615,7 @@ describe API::MergeRequests do end get v3_api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) @@ -631,7 +623,7 @@ describe API::MergeRequests do it 'returns an empty array when there are no issues to be closed' do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -644,7 +636,7 @@ describe API::MergeRequests do get v3_api("/projects/#{jira_project.id}/merge_requests/#{merge_request.id}/closes_issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['title']).to eq(issue.title) @@ -659,7 +651,7 @@ describe API::MergeRequests do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -667,20 +659,20 @@ describe API::MergeRequests do it 'subscribes to a merge request' do post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['subscribed']).to eq(true) end it 'returns 304 if already subscribed' do post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the merge request is not found' do post v3_api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 403 if user has no access to read code' do @@ -689,7 +681,7 @@ describe API::MergeRequests do post v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -697,20 +689,20 @@ describe API::MergeRequests do it 'unsubscribes from a merge request' do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['subscribed']).to eq(false) end it 'returns 304 if not subscribed' do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end it 'returns 404 if the merge request is not found' do post v3_api("/projects/#{project.id}/merge_requests/123/subscription", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns 403 if user has no access to read code' do @@ -719,7 +711,7 @@ describe API::MergeRequests do delete v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", guest) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb index feaa87faec7..e82f35598a6 100644 --- a/spec/requests/api/v3/milestones_spec.rb +++ b/spec/requests/api/v3/milestones_spec.rb @@ -12,7 +12,7 @@ describe API::V3::Milestones do it 'returns project milestones' do get v3_api("/projects/#{project.id}/milestones", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) end @@ -20,13 +20,13 @@ describe API::V3::Milestones do it 'returns a 401 error if user not authenticated' do get v3_api("/projects/#{project.id}/milestones") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns an array of active milestones' do get v3_api("/projects/#{project.id}/milestones?state=active", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(milestone.id) @@ -35,7 +35,7 @@ describe API::V3::Milestones do it 'returns an array of closed milestones' do get v3_api("/projects/#{project.id}/milestones?state=closed", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_milestone.id) @@ -46,7 +46,7 @@ describe API::V3::Milestones do it 'returns a project milestone by id' do get v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -63,7 +63,7 @@ describe API::V3::Milestones do it 'returns a project milestone by iid array' do get v3_api("/projects/#{project.id}/milestones", user), iid: [milestone.iid, closed_milestone.iid] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) expect(json_response.first['title']).to eq milestone.title expect(json_response.first['id']).to eq milestone.id @@ -72,13 +72,13 @@ describe API::V3::Milestones do it 'returns 401 error if user not authenticated' do get v3_api("/projects/#{project.id}/milestones/#{milestone.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 404 error if milestone id not found' do get v3_api("/projects/#{project.id}/milestones/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -86,7 +86,7 @@ describe API::V3::Milestones do it 'creates a new project milestone' do post v3_api("/projects/#{project.id}/milestones", user), title: 'new milestone' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end @@ -95,7 +95,7 @@ describe API::V3::Milestones do post v3_api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') expect(json_response['start_date']).to eq('2013-02-02') @@ -104,20 +104,20 @@ describe API::V3::Milestones do it 'returns a 400 error if title is missing' do post v3_api("/projects/#{project.id}/milestones", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 error if params are invalid (duplicate title)' do post v3_api("/projects/#{project.id}/milestones", user), title: milestone.title, description: 'release', due_date: '2013-03-02' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'creates a new project with reserved html characters' do post v3_api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') expect(json_response['description']).to be_nil end @@ -128,7 +128,7 @@ describe API::V3::Milestones do put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -137,7 +137,7 @@ describe API::V3::Milestones do put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to be_nil end @@ -145,7 +145,7 @@ describe API::V3::Milestones do put v3_api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -153,7 +153,7 @@ describe API::V3::Milestones do it 'updates a project milestone' do put v3_api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') end @@ -175,7 +175,7 @@ describe API::V3::Milestones do it 'returns project issues for a particular milestone' do get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) end @@ -183,14 +183,14 @@ describe API::V3::Milestones do it 'matches V3 response schema for a list of issues' do get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v3/issues') end it 'returns a 401 error if user not authenticated' do get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end describe 'confidential issues' do @@ -207,7 +207,7 @@ describe API::V3::Milestones do it 'returns confidential issues to team members' do get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(2) expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) @@ -219,7 +219,7 @@ describe API::V3::Milestones do get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.map { |issue| issue['id'] }).to include(issue.id) @@ -228,7 +228,7 @@ describe API::V3::Milestones do it 'does not return confidential issues to regular users' do get v3_api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.map { |issue| issue['id'] }).to include(issue.id) diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb index 56729692eed..d3455a4bba4 100644 --- a/spec/requests/api/v3/notes_spec.rb +++ b/spec/requests/api/v3/notes_spec.rb @@ -35,7 +35,7 @@ describe API::V3::Notes do it "returns an array of issue notes" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(issue_note.note) @@ -46,14 +46,14 @@ describe API::V3::Notes do it "returns a 404 error when issue id not found" do get v3_api("/projects/#{project.id}/issues/12345/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "and current user cannot view the notes" do it "returns an empty array" do get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response).to be_empty @@ -65,7 +65,7 @@ describe API::V3::Notes do it "returns 404" do get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -73,7 +73,7 @@ describe API::V3::Notes do it "returns an empty array" do get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(cross_reference_note.note) @@ -86,7 +86,7 @@ describe API::V3::Notes do it "returns an array of snippet notes" do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(snippet_note.note) @@ -95,13 +95,13 @@ describe API::V3::Notes do it "returns a 404 error when snippet id not found" do get v3_api("/projects/#{project.id}/snippets/42/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 when not authorized" do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -109,7 +109,7 @@ describe API::V3::Notes do it "returns an array of merge_requests notes" do get v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['body']).to eq(merge_request_note.note) @@ -118,13 +118,13 @@ describe API::V3::Notes do it "returns a 404 error if merge request id not found" do get v3_api("/projects/#{project.id}/merge_requests/4444/notes", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 404 when not authorized" do get v3_api("/projects/#{project.id}/merge_requests/4444/notes", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -134,21 +134,21 @@ describe API::V3::Notes do it "returns an issue note by id" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(issue_note.note) end it "returns a 404 error if issue note not found" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "and current user cannot view the note" do it "returns a 404 error" do get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context "when issue is confidential" do @@ -157,7 +157,7 @@ describe API::V3::Notes do it "returns 404" do get v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", private_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -165,7 +165,7 @@ describe API::V3::Notes do it "returns an issue note by id" do get v3_api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(cross_reference_note.note) end end @@ -176,14 +176,14 @@ describe API::V3::Notes do it "returns a snippet note by id" do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq(snippet_note.note) end it "returns a 404 error if snippet note not found" do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -193,7 +193,7 @@ describe API::V3::Notes do it "creates a new issue note" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -201,13 +201,13 @@ describe API::V3::Notes do it "returns a 400 bad request error if body not given" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if user not authenticated" do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end context 'when an admin or owner makes the request' do @@ -216,7 +216,7 @@ describe API::V3::Notes do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!', created_at: creation_time - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) @@ -229,7 +229,7 @@ describe API::V3::Notes do it 'creates a new issue note' do post v3_api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') end end @@ -238,7 +238,7 @@ describe API::V3::Notes do it 'creates a new issue note' do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq(':+1:') end end @@ -248,7 +248,7 @@ describe API::V3::Notes do it "creates a new snippet note" do post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) end @@ -256,13 +256,13 @@ describe API::V3::Notes do it "returns a 400 bad request error if body not given" do post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 401 unauthorized error if user not authenticated" do post v3_api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!' - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -274,7 +274,7 @@ describe API::V3::Notes do post v3_api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'Foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -314,7 +314,7 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -322,14 +322,14 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user), body: 'Hello!' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 400 bad request error if body not given' do put v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -338,7 +338,7 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -346,7 +346,7 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user), body: "Hello!" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -355,7 +355,7 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/#{merge_request_note.id}", user), body: 'Hello!' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['body']).to eq('Hello!') end @@ -363,7 +363,7 @@ describe API::V3::Notes do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ "notes/12345", user), body: "Hello!" - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -374,17 +374,17 @@ describe API::V3::Notes do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) # Check if note is really deleted delete v3_api("/projects/#{project.id}/issues/#{issue.id}/"\ "notes/#{issue_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete v3_api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -393,18 +393,18 @@ describe API::V3::Notes do delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) # Check if note is really deleted delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/#{snippet_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}/"\ "notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -413,18 +413,18 @@ describe API::V3::Notes do delete v3_api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) # Check if note is really deleted delete v3_api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/#{merge_request_note.id}", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when note id not found' do delete v3_api("/projects/#{project.id}/merge_requests/"\ "#{merge_request.id}/notes/12345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/pipelines_spec.rb b/spec/requests/api/v3/pipelines_spec.rb index e1d036ff365..1c7d9fe32bb 100644 --- a/spec/requests/api/v3/pipelines_spec.rb +++ b/spec/requests/api/v3/pipelines_spec.rb @@ -32,7 +32,7 @@ describe API::V3::Pipelines do it 'returns project pipelines' do get v3_api("/projects/#{project.id}/pipelines", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['sha']).to match(/\A\h{40}\z/) expect(json_response.first['id']).to eq pipeline.id @@ -44,7 +44,7 @@ describe API::V3::Pipelines do it 'does not return project pipelines' do get v3_api("/projects/#{project.id}/pipelines", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response).not_to be_an Array end @@ -61,7 +61,7 @@ describe API::V3::Pipelines do post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch end.to change { Ci::Pipeline.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a Hash expect(json_response['sha']).to eq project.commit.id end @@ -69,7 +69,7 @@ describe API::V3::Pipelines do it 'fails when using an invalid ref' do post v3_api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Reference not found' expect(json_response).not_to be_an Array end @@ -79,7 +79,7 @@ describe API::V3::Pipelines do it 'fails to create pipeline' do post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' expect(json_response).not_to be_an Array end @@ -90,7 +90,7 @@ describe API::V3::Pipelines do it 'does not create pipeline' do post v3_api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response).not_to be_an Array end @@ -102,14 +102,14 @@ describe API::V3::Pipelines do it 'returns project pipelines' do get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['sha']).to match /\A\h{40}\z/ end it 'returns 404 when it does not exist' do get v3_api("/projects/#{project.id}/pipelines/123456", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Not found' expect(json_response['id']).to be nil end @@ -131,7 +131,7 @@ describe API::V3::Pipelines do it 'should not return a project pipeline' do get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response['id']).to be nil end @@ -152,7 +152,7 @@ describe API::V3::Pipelines do post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) end.to change { pipeline.builds.count }.from(1).to(2) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(build.reload.retried?).to be true end end @@ -161,7 +161,7 @@ describe API::V3::Pipelines do it 'should not return a project pipeline' do post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq '404 Project Not Found' expect(json_response['id']).to be nil end @@ -180,7 +180,7 @@ describe API::V3::Pipelines do it 'retries failed builds' do post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['status']).to eq('canceled') end end @@ -193,7 +193,7 @@ describe API::V3::Pipelines do it 'rejects the action' do post v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(pipeline.reload.status).to eq('pending') end end diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb index b0eddbb5dd2..00f59744a31 100644 --- a/spec/requests/api/v3/project_hooks_spec.rb +++ b/spec/requests/api/v3/project_hooks_spec.rb @@ -22,7 +22,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns project hooks" do get v3_api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.count).to eq(1) expect(json_response.first['url']).to eq("http://example.com") @@ -42,7 +42,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "does not access project hooks" do get v3_api("/projects/#{project.id}/hooks", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -51,7 +51,7 @@ describe API::ProjectHooks, 'ProjectHooks' do context "authorized user" do it "returns a project hook" do get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['url']).to eq(hook.url) expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(hook.push_events) @@ -66,20 +66,20 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 404 error if hook id is not available" do get v3_api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context "unauthorized user" do it "does not access an existing hook" do get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it "returns a 404 error if hook id is not available" do get v3_api("/projects/#{project.id}/hooks/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -90,7 +90,7 @@ describe API::ProjectHooks, 'ProjectHooks' do url: "http://example.com", issues_events: true, wiki_page_events: true, build_events: true end.to change {project.hooks.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['url']).to eq('http://example.com') expect(json_response['issues_events']).to eq(true) expect(json_response['push_events']).to eq(true) @@ -111,7 +111,7 @@ describe API::ProjectHooks, 'ProjectHooks' do post v3_api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token end.to change {project.hooks.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response["url"]).to eq("http://example.com") expect(json_response).not_to include("token") @@ -123,12 +123,12 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns a 400 error if url not given" do post v3_api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 422 error if url not valid" do post v3_api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -136,7 +136,7 @@ describe API::ProjectHooks, 'ProjectHooks' do it "updates an existing project hook" do put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'http://example.org', push_events: false, build_events: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['url']).to eq('http://example.org') expect(json_response['issues_events']).to eq(hook.issues_events) expect(json_response['push_events']).to eq(false) @@ -154,7 +154,7 @@ describe API::ProjectHooks, 'ProjectHooks' do put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response["url"]).to eq("http://example.org") expect(json_response).not_to include("token") @@ -164,17 +164,17 @@ describe API::ProjectHooks, 'ProjectHooks' do it "returns 404 error if hook id not found" do put v3_api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns 400 error if url is not given" do put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 422 error if url is not valid" do put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - expect(response).to have_http_status(422) + expect(response).to have_gitlab_http_status(422) end end @@ -183,23 +183,23 @@ describe API::ProjectHooks, 'ProjectHooks' do expect do delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) end.to change {project.hooks.count}.by(-1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns success when deleting hook" do delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it "returns a 404 error when deleting non existent hook" do delete v3_api("/projects/#{project.id}/hooks/42", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 404 error if hook id not given" do delete v3_api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do @@ -208,7 +208,7 @@ describe API::ProjectHooks, 'ProjectHooks' do other_project.team << [test_user, :master] delete v3_api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(WebHook.exists?(hook.id)).to be_truthy end end diff --git a/spec/requests/api/v3/project_snippets_spec.rb b/spec/requests/api/v3/project_snippets_spec.rb index 7e88489082a..2ed31b99516 100644 --- a/spec/requests/api/v3/project_snippets_spec.rb +++ b/spec/requests/api/v3/project_snippets_spec.rb @@ -28,7 +28,7 @@ describe API::ProjectSnippets do get v3_api("/projects/#{project.id}/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(3) expect(json_response.map { |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) expect(json_response.last).to have_key('web_url') @@ -38,7 +38,7 @@ describe API::ProjectSnippets do create(:project_snippet, :private, project: project) get v3_api("/projects/#{project.id}/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(0) end end @@ -56,7 +56,7 @@ describe API::ProjectSnippets do it 'creates a new snippet' do post v3_api("/projects/#{project.id}/snippets/", admin), params - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) snippet = ProjectSnippet.find(json_response['id']) expect(snippet.content).to eq(params[:code]) expect(snippet.title).to eq(params[:title]) @@ -69,7 +69,7 @@ describe API::ProjectSnippets do post v3_api("/projects/#{project.id}/snippets/", admin), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -95,7 +95,7 @@ describe API::ProjectSnippets do expect { create_snippet(project, visibility_level: Snippet::PUBLIC) } .not_to change { Snippet.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -116,7 +116,7 @@ describe API::ProjectSnippets do put v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) snippet.reload expect(snippet.content).to eq(new_content) end @@ -124,14 +124,14 @@ describe API::ProjectSnippets do it 'returns 404 for invalid snippet id' do put v3_api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it 'returns 400 for missing parameters' do put v3_api("/projects/#{project.id}/snippets/1234", admin) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -173,7 +173,7 @@ describe API::ProjectSnippets do expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) } .not_to change { snippet.reload.title } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq({ "error" => "Spam detected" }) end @@ -194,13 +194,13 @@ describe API::ProjectSnippets do delete v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns 404 for invalid snippet id' do delete v3_api("/projects/#{snippet.project.id}/snippets/1234", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end @@ -211,7 +211,7 @@ describe API::ProjectSnippets do it 'returns raw text' do get v3_api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end @@ -219,7 +219,7 @@ describe API::ProjectSnippets do it 'returns 404 for invalid snippet id' do delete v3_api("/projects/#{snippet.project.id}/snippets/1234", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index cae2c3118da..f62ad747c73 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -44,14 +44,14 @@ describe API::V3::Projects do context 'when unauthenticated' do it 'returns authentication error' do get v3_api('/projects') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated as regular user' do it 'returns an array of projects' do get v3_api('/projects', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(project.name) expect(json_response.first['owner']['username']).to eq(user.username) @@ -89,11 +89,12 @@ describe API::V3::Projects do path path_with_namespace star_count forks_count created_at last_activity_at + avatar_url ) get v3_api('/projects?simple=true', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first.keys).to match_array expected_keys end @@ -102,7 +103,7 @@ describe API::V3::Projects do context 'and using search' do it 'returns searched project' do get v3_api('/projects', user), { search: project.name } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) end @@ -111,21 +112,21 @@ describe API::V3::Projects do context 'and using the visibility filter' do it 'filters based on private visibility param' do get v3_api('/projects', user), { visibility: 'private' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count) end it 'filters based on internal visibility param' do get v3_api('/projects', user), { visibility: 'internal' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count) end it 'filters based on public visibility param' do get v3_api('/projects', user), { visibility: 'public' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count) end @@ -137,7 +138,7 @@ describe API::V3::Projects do it 'returns archived project' do get v3_api('/projects?archived=true', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(archived_project.id) @@ -146,7 +147,7 @@ describe API::V3::Projects do it 'returns non-archived project' do get v3_api('/projects?archived=false', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(project.id) @@ -155,7 +156,7 @@ describe API::V3::Projects do it 'returns all project' do get v3_api('/projects', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) end @@ -169,7 +170,7 @@ describe API::V3::Projects do it 'returns the correct order when sorted by id' do get v3_api('/projects', user), { order_by: 'id', sort: 'desc' } - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['id']).to eq(project3.id) end @@ -183,21 +184,21 @@ describe API::V3::Projects do context 'when unauthenticated' do it 'returns authentication error' do get v3_api('/projects/all') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated as regular user' do it 'returns authentication error' do get v3_api('/projects/all', user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'when authenticated as admin' do it 'returns an array of all projects' do get v3_api('/projects/all', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response).to satisfy do |response| @@ -212,7 +213,7 @@ describe API::V3::Projects do it "does not include statistics by default" do get v3_api('/projects/all', admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first).not_to include('statistics') end @@ -220,7 +221,7 @@ describe API::V3::Projects do it "includes statistics if requested" do get v3_api('/projects/all', admin), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first).to include 'statistics' end @@ -236,14 +237,14 @@ describe API::V3::Projects do context 'when unauthenticated' do it 'returns authentication error' do get v3_api('/projects/owned') - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated as project owner' do it 'returns an array of projects the user owns' do get v3_api('/projects/owned', user4) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(project4.name) expect(json_response.first['owner']['username']).to eq(user4.username) @@ -252,7 +253,7 @@ describe API::V3::Projects do it "does not include statistics by default" do get v3_api('/projects/owned', user4) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first).not_to include('statistics') end @@ -270,7 +271,7 @@ describe API::V3::Projects do get v3_api('/projects/owned', user4), statistics: true - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['statistics']).to eq attributes.stringify_keys end @@ -282,7 +283,7 @@ describe API::V3::Projects do it 'returns the visible projects' do get v3_api('/projects/visible', current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) end @@ -328,7 +329,7 @@ describe API::V3::Projects do it 'returns the starred projects viewable by the user' do get v3_api('/projects/starred', user3) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id) end @@ -340,14 +341,14 @@ describe API::V3::Projects do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) expect { post v3_api('/projects', user2), name: 'foo' } .to change {Project.count}.by(0) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'creates new project without path but with name and returns 201' do expect { post v3_api('/projects', user), name: 'Foo Project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -358,7 +359,7 @@ describe API::V3::Projects do it 'creates new project without name but with path and returns 201' do expect { post v3_api('/projects', user), path: 'foo_project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -369,7 +370,7 @@ describe API::V3::Projects do it 'creates new project name and path and returns 201' do expect { post v3_api('/projects', user), path: 'foo-Project', name: 'Foo Project' } .to change { Project.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project = Project.first @@ -380,12 +381,12 @@ describe API::V3::Projects do it 'creates last project before reaching project limit' do allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) post v3_api('/projects', user2), name: 'foo' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'does not create new project without name or path and return 400' do expect { post v3_api('/projects', user) }.not_to change { Project.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "assigns attributes to project" do @@ -500,7 +501,7 @@ describe API::V3::Projects do it 'does not allow a non-admin to use a restricted visibility level' do post v3_api('/projects', user), @project - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['visibility_level'].first).to( match('restricted by your GitLab administrator') ) @@ -522,14 +523,14 @@ describe API::V3::Projects do it 'should create new project without path and return 201' do expect { post v3_api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end it 'responds with 400 on failure and not project' do expect { post v3_api("/projects/user/#{user.id}", admin) } .not_to change { Project.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('name is missing') end @@ -543,7 +544,7 @@ describe API::V3::Projects do post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -554,7 +555,7 @@ describe API::V3::Projects do project = attributes_for(:project, :public) post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -563,7 +564,7 @@ describe API::V3::Projects do project = attributes_for(:project, { public: true }) post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -572,7 +573,7 @@ describe API::V3::Projects do project = attributes_for(:project, :internal) post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -580,7 +581,7 @@ describe API::V3::Projects do it 'sets a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post v3_api("/projects/user/#{user.id}", admin), project - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -634,7 +635,7 @@ describe API::V3::Projects do it "uploads the file and returns its info" do post v3_api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['alt']).to eq("dk") expect(json_response['url']).to start_with("/uploads/") expect(json_response['url']).to end_with("/dk.png") @@ -648,7 +649,7 @@ describe API::V3::Projects do get v3_api("/projects/#{public_project.id}") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) expect(json_response['default_branch']).to eq(public_project.default_branch) @@ -667,7 +668,7 @@ describe API::V3::Projects do get v3_api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['id']).to eq(project.id) expect(json_response['description']).to eq(project.description) expect(json_response['default_branch']).to eq(project.default_branch) @@ -709,20 +710,20 @@ describe API::V3::Projects do it 'returns a project by path name' do get v3_api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(project.name) end it 'returns a 404 error if not found' do get v3_api('/projects/42', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'returns a 404 error if user is not a member' do other_user = create(:user) get v3_api("/projects/#{project.id}", other_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'handles users with dots' do @@ -730,14 +731,14 @@ describe API::V3::Projects do project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['name']).to eq(project.name) end it 'exposes namespace fields' do get v3_api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['namespace']).to eq({ 'id' => user.namespace.id, 'name' => user.namespace.name, @@ -755,7 +756,7 @@ describe API::V3::Projects do it 'contains permission information' do get v3_api("/projects", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.first['permissions']['project_access']['access_level']) .to eq(Gitlab::Access::MASTER) expect(json_response.first['permissions']['group_access']).to be_nil @@ -767,7 +768,7 @@ describe API::V3::Projects do project.team << [user, :master] get v3_api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['permissions']['project_access']['access_level']) .to eq(Gitlab::Access::MASTER) expect(json_response['permissions']['group_access']).to be_nil @@ -782,7 +783,7 @@ describe API::V3::Projects do it 'sets the owner and return 200' do get v3_api("/projects/#{project2.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['permissions']['project_access']).to be_nil expect(json_response['permissions']['group_access']['access_level']) .to eq(Gitlab::Access::OWNER) @@ -802,7 +803,7 @@ describe API::V3::Projects do get v3_api("/projects/#{project.id}/events", current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) first_event = json_response.first @@ -835,7 +836,7 @@ describe API::V3::Projects do it 'returns a 404 error if not found' do get v3_api('/projects/42/events', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end @@ -844,7 +845,7 @@ describe API::V3::Projects do get v3_api("/projects/#{project.id}/events", other_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -856,7 +857,7 @@ describe API::V3::Projects do get v3_api("/projects/#{project.id}/users", current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -885,7 +886,7 @@ describe API::V3::Projects do it 'returns a 404 error if not found' do get v3_api('/projects/42/users', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end @@ -894,7 +895,7 @@ describe API::V3::Projects do get v3_api("/projects/#{project.id}/users", other_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -904,7 +905,7 @@ describe API::V3::Projects do it 'returns an array of project snippets' do get v3_api("/projects/#{project.id}/snippets", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(snippet.title) end @@ -913,13 +914,13 @@ describe API::V3::Projects do describe 'GET /projects/:id/snippets/:snippet_id' do it 'returns a project snippet' do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(snippet.title) end it 'returns a 404 error if snippet id not found' do get v3_api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -928,7 +929,7 @@ describe API::V3::Projects do post v3_api("/projects/#{project.id}/snippets", user), title: 'v3_api test', file_name: 'sample.rb', code: 'test', visibility_level: '0' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('v3_api test') end @@ -942,7 +943,7 @@ describe API::V3::Projects do it 'updates an existing project snippet' do put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('example') expect(snippet.reload.content).to eq('updated code') end @@ -950,7 +951,7 @@ describe API::V3::Projects do it 'updates an existing project snippet with new title' do put v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other v3_api test' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('other v3_api test') end end @@ -962,24 +963,24 @@ describe API::V3::Projects do expect do delete v3_api("/projects/#{project.id}/snippets/#{snippet.id}", user) end.to change { Snippet.count }.by(-1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns 404 when deleting unknown snippet id' do delete v3_api("/projects/#{project.id}/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end describe 'GET /projects/:id/snippets/:snippet_id/raw' do it 'gets a raw project snippet' do get v3_api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'returns a 404 error if raw project snippet not found' do get v3_api("/projects/#{project.id}/snippets/5555/raw", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -992,13 +993,13 @@ describe API::V3::Projects do it "is not available for non admin users" do post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'allows project to be forked from an existing project' do expect(project_fork_target.forked?).not_to be_truthy post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked_project_link).not_to be_nil @@ -1015,7 +1016,7 @@ describe API::V3::Projects do it 'fails if forked_from project which does not exist' do post v3_api("/projects/#{project_fork_target.id}/fork/9999", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'fails with 409 if already forked' do @@ -1023,7 +1024,7 @@ describe API::V3::Projects do project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) post v3_api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - expect(response).to have_http_status(409) + expect(response).to have_gitlab_http_status(409) project_fork_target.reload expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) expect(project_fork_target.forked?).to be_truthy @@ -1033,7 +1034,7 @@ describe API::V3::Projects do describe 'DELETE /projects/:id/fork' do it "is not visible to users outside group" do delete v3_api("/projects/#{project_fork_target.id}/fork", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end context 'when users belong to project group' do @@ -1046,7 +1047,7 @@ describe API::V3::Projects do it 'is forbidden to non-owner users' do delete v3_api("/projects/#{project_fork_target.id}/fork", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'makes forked project unforked' do @@ -1055,7 +1056,7 @@ describe API::V3::Projects do expect(project_fork_target.forked_from_project).not_to be_nil expect(project_fork_target.forked?).to be_truthy delete v3_api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_fork_target.reload expect(project_fork_target.forked_from_project).to be_nil expect(project_fork_target.forked?).not_to be_truthy @@ -1064,7 +1065,7 @@ describe API::V3::Projects do it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete v3_api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -1081,7 +1082,7 @@ describe API::V3::Projects do post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -1089,18 +1090,18 @@ describe API::V3::Projects do it "returns a 400 error when group id is not given" do post v3_api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when access level is not given" do post v3_api("/projects/#{project.id}/share", user), group_id: group.id - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -1108,19 +1109,19 @@ describe API::V3::Projects do post v3_api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when group does not exist' do post v3_api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it "returns a 400 error when wrong params passed" do post v3_api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'group_access does not have a valid value' end end @@ -1132,26 +1133,26 @@ describe API::V3::Projects do delete v3_api("/projects/#{project.id}/share/#{group.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) expect(project.project_group_links).to be_empty end it 'returns a 400 when group id is not an integer' do delete v3_api("/projects/#{project.id}/share/foo", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 404 error when group link does not exist' do delete v3_api("/projects/#{project.id}/share/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 error when project does not exist' do delete v3_api("/projects/123/share/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -1172,7 +1173,7 @@ describe API::V3::Projects do it 'returns project search responses' do get v3_api("/projects/search/#{args[:query]}", current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(args[:results]) json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*#{args[:query]}.*/) } @@ -1215,7 +1216,7 @@ describe API::V3::Projects do it 'returns authentication error' do project_param = { name: 'bar' } put v3_api("/projects/#{project.id}"), project_param - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -1223,7 +1224,7 @@ describe API::V3::Projects do it 'updates name' do project_param = { name: 'bar' } put v3_api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1232,7 +1233,7 @@ describe API::V3::Projects do it 'updates visibility_level' do project_param = { visibility_level: 20 } put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1242,7 +1243,7 @@ describe API::V3::Projects do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) project_param = { public: false } put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1252,7 +1253,7 @@ describe API::V3::Projects do it 'does not update name to existing name' do project_param = { name: project3.name } put v3_api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['name']).to eq(['has already been taken']) end @@ -1261,14 +1262,14 @@ describe API::V3::Projects do put v3_api("/projects/#{project.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['request_access_enabled']).to eq(false) end it 'updates path & name to existing path & name in different namespace' do project_param = { path: project4.path, name: project4.name } put v3_api("/projects/#{project3.id}", user), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1279,7 +1280,7 @@ describe API::V3::Projects do it 'updates path' do project_param = { path: 'bar' } put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1293,7 +1294,7 @@ describe API::V3::Projects do description: 'new description' } put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project_param.each_pair do |k, v| expect(json_response[k.to_s]).to eq(v) end @@ -1302,20 +1303,20 @@ describe API::V3::Projects do it 'does not update path to existing path' do project_param = { path: project.path } put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['path']).to eq(['has already been taken']) end it 'does not update name' do project_param = { name: 'bar' } put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not update visibility_level' do project_param = { visibility_level: 20 } put v3_api("/projects/#{project3.id}", user4), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1329,7 +1330,7 @@ describe API::V3::Projects do description: 'new description', request_access_enabled: true } put v3_api("/projects/#{project.id}", user3), project_param - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1339,7 +1340,7 @@ describe API::V3::Projects do it 'archives the project' do post v3_api("/projects/#{project.id}/archive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -1352,7 +1353,7 @@ describe API::V3::Projects do it 'remains archived' do post v3_api("/projects/#{project.id}/archive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_truthy end end @@ -1365,7 +1366,7 @@ describe API::V3::Projects do it 'rejects the action' do post v3_api("/projects/#{project.id}/archive", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1375,7 +1376,7 @@ describe API::V3::Projects do it 'remains unarchived' do post v3_api("/projects/#{project.id}/unarchive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1388,7 +1389,7 @@ describe API::V3::Projects do it 'unarchives the project' do post v3_api("/projects/#{project.id}/unarchive", user) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['archived']).to be_falsey end end @@ -1401,7 +1402,7 @@ describe API::V3::Projects do it 'rejects the action' do post v3_api("/projects/#{project.id}/unarchive", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1411,7 +1412,7 @@ describe API::V3::Projects do it 'stars the project' do expect { post v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['star_count']).to eq(1) end end @@ -1425,7 +1426,7 @@ describe API::V3::Projects do it 'does not modify the star count' do expect { post v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end end @@ -1440,7 +1441,7 @@ describe API::V3::Projects do it 'unstars the project' do expect { delete v3_api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(-1) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['star_count']).to eq(0) end end @@ -1449,7 +1450,7 @@ describe API::V3::Projects do it 'does not modify the star count' do expect { delete v3_api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count } - expect(response).to have_http_status(304) + expect(response).to have_gitlab_http_status(304) end end end @@ -1458,36 +1459,36 @@ describe API::V3::Projects do context 'when authenticated as user' do it 'removes project' do delete v3_api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not remove a project if not an owner' do user3 = create(:user) project.team << [user3, :developer] delete v3_api("/projects/#{project.id}", user3) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not remove a non existing project' do delete v3_api('/projects/1328', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'does not remove a project not attached to user' do delete v3_api("/projects/#{project.id}", user2) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end context 'when authenticated as admin' do it 'removes any existing project' do delete v3_api("/projects/#{project.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'does not remove a non existing project' do delete v3_api('/projects/1328', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/repositories_spec.rb b/spec/requests/api/v3/repositories_spec.rb index 1a55e2a71cd..0167eb2c4f6 100644 --- a/spec/requests/api/v3/repositories_spec.rb +++ b/spec/requests/api/v3/repositories_spec.rb @@ -17,7 +17,7 @@ describe API::V3::Repositories do it 'returns the repository tree' do get v3_api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array first_commit = json_response.first @@ -97,20 +97,21 @@ describe API::V3::Repositories do end end - { - 'blobs/:sha' => 'blobs/master', - 'commits/:sha/blob' => 'commits/master/blob' - }.each do |desc_path, example_path| + [ + ['blobs/:sha', 'blobs/master'], + ['blobs/:sha', 'blobs/v1.1.0'], + ['commits/:sha/blob', 'commits/master/blob'] + ].each do |desc_path, example_path| describe "GET /projects/:id/repository/#{desc_path}" do let(:route) { "/projects/#{project.id}/repository/#{example_path}?filepath=README.md" } shared_examples_for 'repository blob' do it 'returns the repository blob' do get v3_api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when sha does not exist' do it_behaves_like '404 response' do - let(:request) { get v3_api(route.sub('master', 'invalid_branch_name'), current_user) } + let(:request) { get v3_api("/projects/#{project.id}/repository/#{desc_path.sub(':sha', 'invalid_branch_name')}?filepath=README.md", current_user) } let(:message) { '404 Commit Not Found' } end end @@ -161,7 +162,7 @@ describe API::V3::Repositories do shared_examples_for 'repository raw blob' do it 'returns the repository raw blob' do get v3_api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end context 'when sha does not exist' do it_behaves_like '404 response' do @@ -204,7 +205,7 @@ describe API::V3::Repositories do shared_examples_for 'repository archive' do it 'returns the repository archive' do get v3_api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data expect(type).to eq('git-archive') @@ -212,7 +213,7 @@ describe API::V3::Repositories do end it 'returns the repository archive archive.zip' do get v3_api("/projects/#{project.id}/repository/archive.zip", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data expect(type).to eq('git-archive') @@ -220,7 +221,7 @@ describe API::V3::Repositories do end it 'returns the repository archive archive.tar.bz2' do get v3_api("/projects/#{project.id}/repository/archive.tar.bz2", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) repo_name = project.repository.name.gsub("\.git", "") type, params = workhorse_send_data expect(type).to eq('git-archive') @@ -262,32 +263,32 @@ describe API::V3::Repositories do shared_examples_for 'repository compare' do it "compares branches" do get v3_api(route, current_user), from: 'master', to: 'feature' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "compares tags" do get v3_api(route, current_user), from: 'v1.0.0', to: 'v1.1.0' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "compares commits" do get v3_api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_falsey end it "compares commits in reverse order" do get v3_api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_present expect(json_response['diffs']).to be_present end it "compares same refs" do get v3_api(route, current_user), from: 'master', to: 'master' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['commits']).to be_empty expect(json_response['diffs']).to be_empty expect(json_response['compare_same_ref']).to be_truthy @@ -324,7 +325,7 @@ describe API::V3::Repositories do it 'returns valid data' do get v3_api(route, current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array first_contributor = json_response.first diff --git a/spec/requests/api/v3/runners_spec.rb b/spec/requests/api/v3/runners_spec.rb index a31eb3f1d43..c91b097a3c7 100644 --- a/spec/requests/api/v3/runners_spec.rb +++ b/spec/requests/api/v3/runners_spec.rb @@ -37,7 +37,7 @@ describe API::V3::Runners do expect do delete v3_api("/runners/#{shared_runner.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { Ci::Runner.shared.count }.by(-1) end end @@ -47,7 +47,7 @@ describe API::V3::Runners do expect do delete v3_api("/runners/#{unused_specific_runner.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { Ci::Runner.specific.count }.by(-1) end @@ -55,7 +55,7 @@ describe API::V3::Runners do expect do delete v3_api("/runners/#{specific_runner.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { Ci::Runner.specific.count }.by(-1) end end @@ -63,7 +63,7 @@ describe API::V3::Runners do it 'returns 404 if runner does not exists' do delete v3_api('/runners/9999', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -71,26 +71,26 @@ describe API::V3::Runners do context 'when runner is shared' do it 'does not delete runner' do delete v3_api("/runners/#{shared_runner.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end context 'when runner is not shared' do it 'does not delete runner without access to it' do delete v3_api("/runners/#{specific_runner.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'does not delete runner with more than one associated project' do delete v3_api("/runners/#{two_projects_runner.id}", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'deletes runner for one owned project' do expect do delete v3_api("/runners/#{specific_runner.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { Ci::Runner.specific.count }.by(-1) end end @@ -100,7 +100,7 @@ describe API::V3::Runners do it 'does not delete runner' do delete v3_api("/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -112,7 +112,7 @@ describe API::V3::Runners do expect do delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { project.runners.count }.by(-1) end end @@ -122,14 +122,14 @@ describe API::V3::Runners do expect do delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user) end.to change { project.runners.count }.by(0) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end it 'returns 404 is runner is not found' do delete v3_api("/projects/#{project.id}/runners/9999", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -137,7 +137,7 @@ describe API::V3::Runners do it "does not disable project's runner" do delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -145,7 +145,7 @@ describe API::V3::Runners do it "does not disable project's runner" do delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb index f0fa48e22df..8f212ab6be6 100644 --- a/spec/requests/api/v3/services_spec.rb +++ b/spec/requests/api/v3/services_spec.rb @@ -13,7 +13,7 @@ describe API::V3::Services do it "deletes #{service}" do delete v3_api("/projects/#{project.id}/services/#{dashed_service}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb index 291f6dcc2aa..25fa0a8aabd 100644 --- a/spec/requests/api/v3/settings_spec.rb +++ b/spec/requests/api/v3/settings_spec.rb @@ -7,7 +7,7 @@ describe API::V3::Settings, 'Settings' do describe "GET /application/settings" do it "returns application settings" do get v3_api("/application/settings", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Hash expect(json_response['default_projects_limit']).to eq(42) expect(json_response['password_authentication_enabled']).to be_truthy @@ -30,7 +30,7 @@ describe API::V3::Settings, 'Settings' do put v3_api("/application/settings", admin), default_projects_limit: 3, password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com', plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) expect(json_response['password_authentication_enabled']).to be_falsey expect(json_response['repository_storage']).to eq('custom') @@ -46,7 +46,7 @@ describe API::V3::Settings, 'Settings' do it "returns a blank parameter error message" do put v3_api("/application/settings", admin), koding_enabled: true - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('koding_url is missing') end end @@ -55,7 +55,7 @@ describe API::V3::Settings, 'Settings' do it "returns a blank parameter error message" do put v3_api("/application/settings", admin), plantuml_enabled: true - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('plantuml_url is missing') end end diff --git a/spec/requests/api/v3/snippets_spec.rb b/spec/requests/api/v3/snippets_spec.rb index 79860725634..e8913039194 100644 --- a/spec/requests/api/v3/snippets_spec.rb +++ b/spec/requests/api/v3/snippets_spec.rb @@ -11,7 +11,7 @@ describe API::V3::Snippets do get v3_api("/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( public_snippet.id, internal_snippet.id, @@ -24,7 +24,7 @@ describe API::V3::Snippets do create(:personal_snippet, :private) get v3_api("/snippets/", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(0) end end @@ -41,7 +41,7 @@ describe API::V3::Snippets do it 'returns all snippets with public visibility from all users' do get v3_api("/snippets/public", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( public_snippet.id, public_snippet_other.id) @@ -60,7 +60,7 @@ describe API::V3::Snippets do it 'returns raw text' do get v3_api("/snippets/#{snippet.id}/raw", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response.content_type).to eq 'text/plain' expect(response.body).to eq(snippet.content) end @@ -68,7 +68,7 @@ describe API::V3::Snippets do it 'returns 404 for invalid snippet id' do delete v3_api("/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end @@ -88,7 +88,7 @@ describe API::V3::Snippets do post v3_api("/snippets/", user), params end.to change { PersonalSnippet.count }.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq(params[:title]) expect(json_response['file_name']).to eq(params[:file_name]) end @@ -98,7 +98,7 @@ describe API::V3::Snippets do post v3_api("/snippets/", user), params - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end context 'when the snippet is spam' do @@ -121,7 +121,7 @@ describe API::V3::Snippets do it 'rejects the shippet' do expect { create_snippet(visibility_level: Snippet::PUBLIC) } .not_to change { Snippet.count } - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'creates a spam log' do @@ -140,7 +140,7 @@ describe API::V3::Snippets do put v3_api("/snippets/#{public_snippet.id}", user), content: new_content - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) public_snippet.reload expect(public_snippet.content).to eq(new_content) end @@ -148,21 +148,21 @@ describe API::V3::Snippets do it 'returns 404 for invalid snippet id' do put v3_api("/snippets/1234", user), title: 'foo' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it "returns 404 for another user's snippet" do put v3_api("/snippets/#{public_snippet.id}", other_user), title: 'fubar' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end it 'returns 400 for missing parameters' do put v3_api("/snippets/1234", user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -172,14 +172,14 @@ describe API::V3::Snippets do expect do delete v3_api("/snippets/#{public_snippet.id}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change { PersonalSnippet.count }.by(-1) end it 'returns 404 for invalid snippet id' do delete v3_api("/snippets/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Snippet Not Found') end end diff --git a/spec/requests/api/v3/system_hooks_spec.rb b/spec/requests/api/v3/system_hooks_spec.rb index ae427541abb..30711c60faa 100644 --- a/spec/requests/api/v3/system_hooks_spec.rb +++ b/spec/requests/api/v3/system_hooks_spec.rb @@ -12,7 +12,7 @@ describe API::V3::SystemHooks do it "returns authentication error" do get v3_api("/hooks") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -20,7 +20,7 @@ describe API::V3::SystemHooks do it "returns forbidden error" do get v3_api("/hooks", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -28,7 +28,7 @@ describe API::V3::SystemHooks do it "returns an array of hooks" do get v3_api("/hooks", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['url']).to eq(hook.url) expect(json_response.first['push_events']).to be false @@ -43,14 +43,14 @@ describe API::V3::SystemHooks do expect do delete v3_api("/hooks/#{hook.id}", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change { SystemHook.count }.by(-1) end it 'returns 404 if the system hook does not exist' do delete v3_api('/hooks/12345', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/tags_spec.rb b/spec/requests/api/v3/tags_spec.rb index 1c4b25c47c3..e6ad005fa87 100644 --- a/spec/requests/api/v3/tags_spec.rb +++ b/spec/requests/api/v3/tags_spec.rb @@ -17,7 +17,7 @@ describe API::V3::Tags do it 'returns the repository tags' do get v3_api("/projects/#{project.id}/repository/tags", current_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) end @@ -40,7 +40,7 @@ describe API::V3::Tags do it "returns an array of project tags" do get v3_api("/projects/#{project.id}/repository/tags", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) end @@ -55,7 +55,7 @@ describe API::V3::Tags do it "returns an array of project tags with release info" do get v3_api("/projects/#{project.id}/repository/tags", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) expect(json_response.first['message']).to eq('Version 1.1.0') @@ -75,13 +75,13 @@ describe API::V3::Tags do it 'deletes an existing tag' do delete v3_api("/projects/#{project.id}/repository/tags/#{tag_name}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['tag_name']).to eq(tag_name) end it 'raises 404 if the tag does not exist' do delete v3_api("/projects/#{project.id}/repository/tags/foobar", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/templates_spec.rb b/spec/requests/api/v3/templates_spec.rb index 00446c7f29c..38a8994eb79 100644 --- a/spec/requests/api/v3/templates_spec.rb +++ b/spec/requests/api/v3/templates_spec.rb @@ -19,7 +19,7 @@ describe API::V3::Templates do it 'returns a list of available gitignore templates' do get v3_api(path) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to be > 15 end @@ -29,7 +29,7 @@ describe API::V3::Templates do it 'returns a list of available gitlab_ci_ymls' do get v3_api(path) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['name']).not_to be_nil end @@ -39,7 +39,7 @@ describe API::V3::Templates do it 'adds a disclaimer on the top' do get v3_api(path) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['content']).to start_with("# This file is a template,") end end @@ -66,7 +66,7 @@ describe API::V3::Templates do it 'returns a list of available license templates' do get v3_api(path) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(12) expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') @@ -77,7 +77,7 @@ describe API::V3::Templates do it 'returns a list of available popular license templates' do get v3_api("#{path}?popular=1") - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(3) expect(json_response.map { |l| l['key'] }).to include('apache-2.0') @@ -159,7 +159,7 @@ describe API::V3::Templates do let(:license_type) { 'muth-over9000' } it 'returns a 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb index 7ccf387f2dc..e8e2f49d7a0 100644 --- a/spec/requests/api/v3/triggers_spec.rb +++ b/spec/requests/api/v3/triggers_spec.rb @@ -27,17 +27,17 @@ describe API::V3::Triggers do context 'Handles errors' do it 'returns bad request if token is missing' do post v3_api("/projects/#{project.id}/trigger/builds"), ref: 'master' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns not found if project is not found' do post v3_api('/projects/0/trigger/builds'), options.merge(ref: 'master') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns unauthorized if token is for different project' do post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master') - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -46,7 +46,7 @@ describe API::V3::Triggers do it 'creates builds' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) pipeline.builds.reload expect(pipeline.builds.pending.size).to eq(2) expect(pipeline.builds.size).to eq(5) @@ -54,7 +54,7 @@ describe API::V3::Triggers do it 'returns bad request with no builds created if there\'s no commit for that ref' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['base']) .to contain_exactly('Reference not found') end @@ -66,19 +66,19 @@ describe API::V3::Triggers do it 'validates variables to be a hash' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq('variables is invalid') end it 'validates variables needs to be a map of key-valued strings' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master') - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') end it 'creates trigger request with variables' do post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master') - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) pipeline.builds.reload expect(pipeline.variables.map { |v| { v.key => v.value } }.first).to eq(variables) expect(json_response['variables']).to eq(variables) @@ -91,7 +91,7 @@ describe API::V3::Triggers do expect do post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(5) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end context 'when ref contains a dot' do @@ -102,7 +102,7 @@ describe API::V3::Triggers do post v3_api("/projects/#{project.id}/ref/v.1-branch/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' } end.to change(project.builds, :count).by(4) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) end end end @@ -113,7 +113,7 @@ describe API::V3::Triggers do it 'returns list of triggers' do get v3_api("/projects/#{project.id}/triggers", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_a(Array) expect(json_response[0]).to have_key('token') @@ -124,7 +124,7 @@ describe API::V3::Triggers do it 'does not return triggers list' do get v3_api("/projects/#{project.id}/triggers", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -132,7 +132,7 @@ describe API::V3::Triggers do it 'does not return triggers list' do get v3_api("/projects/#{project.id}/triggers") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -142,14 +142,14 @@ describe API::V3::Triggers do it 'returns trigger details' do get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a(Hash) end it 'responds with 404 Not Found if requesting non-existing trigger' do get v3_api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -157,7 +157,7 @@ describe API::V3::Triggers do it 'does not return triggers list' do get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -165,7 +165,7 @@ describe API::V3::Triggers do it 'does not return triggers list' do get v3_api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -177,7 +177,7 @@ describe API::V3::Triggers do post v3_api("/projects/#{project.id}/triggers", user) end.to change {project.triggers.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response).to be_a(Hash) end end @@ -186,7 +186,7 @@ describe API::V3::Triggers do it 'does not create trigger' do post v3_api("/projects/#{project.id}/triggers", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -194,7 +194,7 @@ describe API::V3::Triggers do it 'does not create trigger' do post v3_api("/projects/#{project.id}/triggers") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -205,14 +205,14 @@ describe API::V3::Triggers do expect do delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end.to change {project.triggers.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing trigger' do delete v3_api("/projects/#{project.id}/triggers/abcdef012345", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -220,7 +220,7 @@ describe API::V3::Triggers do it 'does not delete trigger' do delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -228,7 +228,7 @@ describe API::V3::Triggers do it 'does not delete trigger' do delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb index 227b8d1b0c1..bbd05f240d2 100644 --- a/spec/requests/api/v3/users_spec.rb +++ b/spec/requests/api/v3/users_spec.rb @@ -12,7 +12,7 @@ describe API::V3::Users do it 'returns an array of users' do get v3_api('/users', user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array username = user.username @@ -45,14 +45,14 @@ describe API::V3::Users do context 'when unauthenticated' do it 'returns authentication error' do get v3_api("/users/#{user.id}/keys") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated' do it 'returns 404 for non-existing user' do get v3_api('/users/999999/keys', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -62,7 +62,7 @@ describe API::V3::Users do get v3_api("/users/#{user.id}/keys", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(key.title) end @@ -88,14 +88,14 @@ describe API::V3::Users do context 'when unauthenticated' do it 'returns authentication error' do get v3_api("/users/#{user.id}/emails") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when authenticated' do it 'returns 404 for non-existing user' do get v3_api('/users/999999/emails', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -105,7 +105,7 @@ describe API::V3::Users do get v3_api("/users/#{user.id}/emails", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first['email']).to eq(email.email) end @@ -113,7 +113,7 @@ describe API::V3::Users do it "returns a 404 for invalid ID" do put v3_api("/users/ASDF/emails", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -122,7 +122,7 @@ describe API::V3::Users do context "when unauthenticated" do it "returns authentication error" do get v3_api("/user/keys") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -133,7 +133,7 @@ describe API::V3::Users do get v3_api("/user/keys", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first["title"]).to eq(key.title) end @@ -144,7 +144,7 @@ describe API::V3::Users do context "when unauthenticated" do it "returns authentication error" do get v3_api("/user/emails") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -155,7 +155,7 @@ describe API::V3::Users do get v3_api("/user/emails", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.first["email"]).to eq(email.email) end @@ -166,25 +166,25 @@ describe API::V3::Users do before { admin } it 'blocks existing user' do put v3_api("/users/#{user.id}/block", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.state).to eq('blocked') end it 'does not re-block ldap blocked users' do put v3_api("/users/#{ldap_blocked_user.id}/block", admin) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'does not be available for non admin users' do put v3_api("/users/#{user.id}/block", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(user.reload.state).to eq('active') end it 'returns a 404 error if user id not found' do put v3_api('/users/9999/block', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end @@ -195,38 +195,38 @@ describe API::V3::Users do it 'unblocks existing user' do put v3_api("/users/#{user.id}/unblock", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(user.reload.state).to eq('active') end it 'unblocks a blocked user' do put v3_api("/users/#{blocked_user.id}/unblock", admin) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(blocked_user.reload.state).to eq('active') end it 'does not unblock ldap blocked users' do put v3_api("/users/#{ldap_blocked_user.id}/unblock", admin) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(ldap_blocked_user.reload.state).to eq('ldap_blocked') end it 'does not be available for non admin users' do put v3_api("/users/#{user.id}/unblock", user) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(user.reload.state).to eq('active') end it 'returns a 404 error if user id not found' do put v3_api('/users/9999/block', admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do put v3_api("/users/ASDF/block", admin) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -246,7 +246,7 @@ describe API::V3::Users do get api("/users/#{user.id}/events", other_user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_empty end end @@ -262,7 +262,7 @@ describe API::V3::Users do end it 'responds with HTTP 200 OK' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'includes the push payload as a Hash' do @@ -281,7 +281,7 @@ describe API::V3::Users do it 'returns the "joined" event' do get v3_api("/users/#{user.id}/events", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array @@ -327,7 +327,7 @@ describe API::V3::Users do it 'returns a 404 error if not found' do get v3_api('/users/420/events', user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end end diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 48592e12822..79ee6c126f6 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -13,7 +13,7 @@ describe API::Variables do it 'returns project variables' do get api("/projects/#{project.id}/variables", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_a(Array) end end @@ -22,7 +22,7 @@ describe API::Variables do it 'does not return project variables' do get api("/projects/#{project.id}/variables", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -30,7 +30,7 @@ describe API::Variables do it 'does not return project variables' do get api("/projects/#{project.id}/variables") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -40,7 +40,7 @@ describe API::Variables do it 'returns project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['value']).to eq(variable.value) expect(json_response['protected']).to eq(variable.protected?) end @@ -48,7 +48,7 @@ describe API::Variables do it 'responds with 404 Not Found if requesting non-existing variable' do get api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -56,7 +56,7 @@ describe API::Variables do it 'does not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -64,7 +64,7 @@ describe API::Variables do it 'does not return project variable details' do get api("/projects/#{project.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -76,7 +76,7 @@ describe API::Variables do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true end.to change {project.variables.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_truthy @@ -87,7 +87,7 @@ describe API::Variables do post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2' end.to change {project.variables.count}.by(1) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['key']).to eq('TEST_VARIABLE_2') expect(json_response['value']).to eq('VALUE_2') expect(json_response['protected']).to be_falsey @@ -98,7 +98,7 @@ describe API::Variables do post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2' end.to change {project.variables.count}.by(0) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end end @@ -106,7 +106,7 @@ describe API::Variables do it 'does not create variable' do post api("/projects/#{project.id}/variables", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -114,7 +114,7 @@ describe API::Variables do it 'does not create variable' do post api("/projects/#{project.id}/variables") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -129,7 +129,7 @@ describe API::Variables do updated_variable = project.variables.first - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(value_before).to eq(variable.value) expect(updated_variable.value).to eq('VALUE_1_UP') expect(updated_variable).to be_protected @@ -138,7 +138,7 @@ describe API::Variables do it 'responds with 404 Not Found if requesting non-existing variable' do put api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -146,7 +146,7 @@ describe API::Variables do it 'does not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -154,7 +154,7 @@ describe API::Variables do it 'does not update variable' do put api("/projects/#{project.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -165,14 +165,14 @@ describe API::Variables do expect do delete api("/projects/#{project.id}/variables/#{variable.key}", user) - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end.to change {project.variables.count}.by(-1) end it 'responds with 404 Not Found if requesting non-existing variable' do delete api("/projects/#{project.id}/variables/non_existing_variable", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -180,7 +180,7 @@ describe API::Variables do it 'does not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}", user2) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -188,7 +188,7 @@ describe API::Variables do it 'does not delete variable' do delete api("/projects/#{project.id}/variables/#{variable.key}") - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index 9e889d1eecf..65bd001e491 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -26,7 +26,7 @@ describe API::Wikis do it 'returns the list of wiki pages without content' do get api(url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) json_response.each_with_index do |page, index| @@ -39,7 +39,7 @@ describe API::Wikis do it 'returns the list of wiki pages with content' do get api(url, user), with_content: 1 - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) json_response.each_with_index do |page, index| @@ -54,14 +54,14 @@ describe API::Wikis do it 'return the empty list of wiki pages' do get api(url, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(0) end end shared_examples_for 'returns wiki page' do it 'returns the wiki page' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(4) expect(json_response.keys).to match_array(expected_keys_with_content) expect(json_response['content']).to eq(page.content) @@ -74,7 +74,7 @@ describe API::Wikis do it 'creates the wiki page' do post(api(url, user), payload) - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response.size).to eq(4) expect(json_response.keys).to match_array(expected_keys_with_content) expect(json_response['content']).to eq(payload[:content]) @@ -89,7 +89,7 @@ describe API::Wikis do post(api(url, user), payload) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response.size).to eq(1) expect(json_response['error']).to eq("#{part} is missing") end @@ -98,7 +98,7 @@ describe API::Wikis do shared_examples_for 'updates wiki page' do it 'updates the wiki page' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(4) expect(json_response.keys).to match_array(expected_keys_with_content) expect(json_response['content']).to eq(payload[:content]) @@ -109,7 +109,7 @@ describe API::Wikis do shared_examples_for '403 Forbidden' do it 'returns 403 Forbidden' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response.size).to eq(1) expect(json_response['message']).to eq('403 Forbidden') end @@ -117,7 +117,7 @@ describe API::Wikis do shared_examples_for '404 Wiki Page Not Found' do it 'returns 404 Wiki Page Not Found' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response.size).to eq(1) expect(json_response['message']).to eq('404 Wiki Page Not Found') end @@ -125,7 +125,7 @@ describe API::Wikis do shared_examples_for '404 Project Not Found' do it 'returns 404 Project Not Found' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) expect(json_response.size).to eq(1) expect(json_response['message']).to eq('404 Project Not Found') end @@ -133,7 +133,7 @@ describe API::Wikis do shared_examples_for '204 No Content' do it 'returns 204 No Content' do - expect(response).to have_http_status(204) + expect(response).to have_gitlab_http_status(204) end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index ecac40e301b..cd52194033a 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -9,7 +9,7 @@ describe 'Git HTTP requests' do context "when no credentials are provided" do it "responds to downloads with status 401 Unauthorized (no project existence information leak)" do download(path) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -18,7 +18,7 @@ describe 'Git HTTP requests' do context "when only username is provided" do it "responds to downloads with status 401 Unauthorized" do download(path, user: user.username) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -28,7 +28,7 @@ describe 'Git HTTP requests' do context "when authentication fails" do it "responds to downloads with status 401 Unauthorized" do download(path, user: user.username, password: "wrong-password") do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -37,7 +37,7 @@ describe 'Git HTTP requests' do context "when authentication succeeds" do it "does not respond to downloads with status 401 Unauthorized" do download(path, user: user.username, password: user.password) do |response| - expect(response).not_to have_http_status(:unauthorized) + expect(response).not_to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to be_nil end end @@ -49,7 +49,7 @@ describe 'Git HTTP requests' do context "when no credentials are provided" do it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do upload(path) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -58,7 +58,7 @@ describe 'Git HTTP requests' do context "when only username is provided" do it "responds to uploads with status 401 Unauthorized" do upload(path, user: user.username) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -68,7 +68,7 @@ describe 'Git HTTP requests' do context "when authentication fails" do it "responds to uploads with status 401 Unauthorized" do upload(path, user: user.username, password: "wrong-password") do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to start_with('Basic ') end end @@ -77,7 +77,7 @@ describe 'Git HTTP requests' do context "when authentication succeeds" do it "does not respond to uploads with status 401 Unauthorized" do upload(path, user: user.username, password: user.password) do |response| - expect(response).not_to have_http_status(:unauthorized) + expect(response).not_to have_gitlab_http_status(:unauthorized) expect(response.header['WWW-Authenticate']).to be_nil end end @@ -88,7 +88,7 @@ describe 'Git HTTP requests' do shared_examples_for 'pulls are allowed' do it do download(path, env) do |response| - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -97,7 +97,7 @@ describe 'Git HTTP requests' do shared_examples_for 'pushes are allowed' do it do upload(path, env) do |response| - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -115,7 +115,7 @@ describe 'Git HTTP requests' do context 'when authenticated' do it 'rejects downloads and uploads with 404 Not Found' do download_or_upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -165,7 +165,7 @@ describe 'Git HTTP requests' do it 'rejects pushes with 403 Forbidden' do upload(path, env) do |response| - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_wiki_error(:write_to_wiki)) end end @@ -190,13 +190,13 @@ describe 'Git HTTP requests' do it 'allows clones' do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end it 'pushes are allowed' do upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end end @@ -205,14 +205,14 @@ describe 'Git HTTP requests' do context 'and not on the team' do it 'rejects clones with 404 Not Found' do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to eq(git_access_error(:project_not_found)) end end it 'rejects pushes with 404 Not Found' do upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to eq(git_access_error(:project_not_found)) end end @@ -253,7 +253,7 @@ describe 'Git HTTP requests' do it 'rejects pushes with 403 Forbidden' do upload(path, env) do |response| - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http)) end end @@ -264,7 +264,7 @@ describe 'Git HTTP requests' do allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false) download(path, env) do |response| - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http)) end end @@ -276,7 +276,7 @@ describe 'Git HTTP requests' do it 'rejects pushes with 403 Forbidden' do upload(path, env) do |response| - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(change_access_error(:push_code)) end end @@ -332,7 +332,7 @@ describe 'Git HTTP requests' do it 'downloads get status 404 with "project was moved" message' do clone_get(path, {}) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to match(project_moved_message) end end @@ -355,7 +355,7 @@ describe 'Git HTTP requests' do clone_get(path, env) - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -374,7 +374,7 @@ describe 'Git HTTP requests' do project.team << [user, :master] download(path, env) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end @@ -382,7 +382,7 @@ describe 'Git HTTP requests' do user.block download('doesnt/exist.git', env) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) end end end @@ -392,7 +392,7 @@ describe 'Git HTTP requests' do expect(Rack::Attack::Allow2Ban).to receive(:reset).twice download(path, env) do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -401,7 +401,7 @@ describe 'Git HTTP requests' do expect(Rack::Attack::Allow2Ban).to receive(:reset).twice upload(path, env) do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -440,14 +440,14 @@ describe 'Git HTTP requests' do context 'when username and password are provided' do it 'rejects pulls with personal access token error message' do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end it 'rejects the push attempt with personal access token error message' do upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -468,14 +468,14 @@ describe 'Git HTTP requests' do it 'rejects pulls with personal access token error message' do download(path, user: 'foo', password: 'bar') do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end it 'rejects pushes with personal access token error message' do upload(path, user: 'foo', password: 'bar') do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -489,7 +489,7 @@ describe 'Git HTTP requests' do it 'does not display the personal access token error message' do upload(path, user: 'foo', password: 'bar') do |response| - expect(response).to have_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:unauthorized) expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -541,13 +541,13 @@ describe 'Git HTTP requests' do it 'downloads get status 404 with "project was moved" message' do clone_get(path, env) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to match(project_moved_message) end it 'uploads get status 404 with "project was moved" message' do upload(path, env) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to match(project_moved_message) end end @@ -557,13 +557,13 @@ describe 'Git HTTP requests' do context "when the user doesn't have access to the project" do it "pulls get status 404" do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end it "uploads get status 404" do upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -595,7 +595,7 @@ describe 'Git HTTP requests' do it "rejects pushes with 403 Forbidden" do push_get(path, env) - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_error(:upload)) end @@ -604,7 +604,7 @@ describe 'Git HTTP requests' do it "rejects pulls for other project with 404 Not Found" do clone_get("#{other_project.full_path}.git", env) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) expect(response.body).to eq(git_access_error(:project_not_found)) end end @@ -627,7 +627,7 @@ describe 'Git HTTP requests' do it 'rejects pulls with 403 Forbidden' do clone_get path, env - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_error(:no_repo)) end end @@ -635,7 +635,7 @@ describe 'Git HTTP requests' do it 'rejects pushes with 403 Forbidden' do push_get path, env - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) expect(response.body).to eq(git_access_error(:upload)) end end @@ -648,7 +648,7 @@ describe 'Git HTTP requests' do it 'downloads from other project get status 403' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) end end @@ -660,7 +660,7 @@ describe 'Git HTTP requests' do it 'downloads from other project get status 404' do clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -748,7 +748,7 @@ describe 'Git HTTP requests' do end it "returns the file" do - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end @@ -758,7 +758,7 @@ describe 'Git HTTP requests' do end it "returns not found" do - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -783,7 +783,7 @@ describe 'Git HTTP requests' do context "when the project doesn't exist" do it "responds with status 404 Not Found" do download(path, user: user.username, password: user.password) do |response| - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end end end @@ -800,7 +800,7 @@ describe 'Git HTTP requests' do it "responds with status 200" do clone_get(path, env) do |response| - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 41bf43a9bce..94e04ce5608 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -13,12 +13,12 @@ describe JwtController do context 'existing service' do subject! { get '/jwt/auth', parameters } - it { expect(response).to have_http_status(200) } + it { expect(response).to have_gitlab_http_status(200) } context 'returning custom http code' do let(:service) { double(execute: { http_status: 505 }) } - it { expect(response).to have_http_status(505) } + it { expect(response).to have_gitlab_http_status(505) } end end @@ -41,7 +41,7 @@ describe JwtController do subject! { get '/jwt/auth', parameters, headers } - it { expect(response).to have_http_status(401) } + it { expect(response).to have_gitlab_http_status(401) } end context 'using personal access tokens' do @@ -56,7 +56,7 @@ describe JwtController do subject! { get '/jwt/auth', parameters, headers } it 'authenticates correctly' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(service_class).to have_received(:new).with(nil, user, parameters) end end @@ -75,7 +75,7 @@ describe JwtController do context 'without personal token' do it 'rejects the authorization attempt' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -85,7 +85,7 @@ describe JwtController do let(:headers) { { authorization: credentials(user.username, access_token.token) } } it 'accepts the authorization attempt' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -98,7 +98,7 @@ describe JwtController do it 'rejects the authorization attempt' do get '/jwt/auth', parameters, headers - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -108,7 +108,7 @@ describe JwtController do allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false } get '/jwt/auth', parameters, headers - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -119,7 +119,7 @@ describe JwtController do it 'accepts the authorization attempt' do get '/jwt/auth', parameters - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'allows read access' do @@ -132,7 +132,7 @@ describe JwtController do context 'unknown service' do subject! { get '/jwt/auth', service: 'unknown' } - it { expect(response).to have_http_status(404) } + it { expect(response).to have_gitlab_http_status(404) } end def credentials(login, password) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 27d09b8202e..52e93e157f1 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'Git LFS API and storage' do include WorkhorseHelpers + include ProjectForksHelper let(:user) { create(:user) } let!(:lfs_object) { create(:lfs_object, :with_file) } @@ -40,7 +41,7 @@ describe 'Git LFS API and storage' do end it 'responds with 501' do - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.') end end @@ -74,13 +75,13 @@ describe 'Git LFS API and storage' do it 'responds with a 501 message on upload' do post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) end it 'responds with a 501 message on download' do get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) end end @@ -92,13 +93,13 @@ describe 'Git LFS API and storage' do it 'responds with a 501 message on upload' do post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) end it 'responds with a 501 message on download' do get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) end end end @@ -117,14 +118,14 @@ describe 'Git LFS API and storage' do it 'responds with a 403 message on upload' do post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response).to include('message' => 'Access forbidden. Check your access level.') end it 'responds with a 403 message on download' do get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) expect(json_response).to include('message' => 'Access forbidden. Check your access level.') end end @@ -137,14 +138,14 @@ describe 'Git LFS API and storage' do it 'responds with a 200 message on upload' do post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['objects'].first['size']).to eq(1575078) end it 'responds with a 200 message on download' do get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end end @@ -159,7 +160,7 @@ describe 'Git LFS API and storage' do shared_examples 'a deprecated' do it 'responds with 501' do - expect(response).to have_http_status(501) + expect(response).to have_gitlab_http_status(501) end it 'returns deprecated message' do @@ -200,7 +201,7 @@ describe 'Git LFS API and storage' do context 'and request comes from gitlab-workhorse' do context 'without user being authorized' do it 'responds with status 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -209,7 +210,7 @@ describe 'Git LFS API and storage' do let(:sendfile) { 'X-Sendfile' } it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with the file location' do @@ -227,7 +228,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -271,7 +272,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -310,7 +311,7 @@ describe 'Git LFS API and storage' do end it 'rejects downloading code' do - expect(response).to have_http_status(other_project_status) + expect(response).to have_gitlab_http_status(other_project_status) end end end @@ -350,7 +351,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } it 'responds with status 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -386,7 +387,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'with href to download' do @@ -414,7 +415,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'with href to download' do @@ -445,7 +446,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'with an 404 for specific object' do @@ -482,7 +483,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with upload hypermedia link for the new object' do @@ -527,7 +528,7 @@ describe 'Git LFS API and storage' do let(:update_user_permissions) { nil } it 'responds with 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -535,7 +536,7 @@ describe 'Git LFS API and storage' do let(:role) { :guest } it 'responds with 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -563,7 +564,7 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } it 'rejects downloading code' do - expect(response).to have_http_status(other_project_status) + expect(response).to have_gitlab_http_status(other_project_status) end end end @@ -607,7 +608,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200 and href to download' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with status 200 and href to download' do @@ -635,7 +636,7 @@ describe 'Git LFS API and storage' do end it 'responds with authorization required' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -668,7 +669,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with links the object to the project' do @@ -694,7 +695,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with upload hypermedia link' do @@ -724,7 +725,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'responds with upload hypermedia link for the new object' do @@ -746,7 +747,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_user } it 'responds with 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -760,7 +761,7 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -771,7 +772,7 @@ describe 'Git LFS API and storage' do # I'm not sure what this tests that is different from the previous test it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -780,7 +781,7 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -793,13 +794,13 @@ describe 'Git LFS API and storage' do end it 'responds with status 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end context 'when user does not have push access' do it 'responds with status 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -819,11 +820,39 @@ describe 'Git LFS API and storage' do end it 'responds with status 404' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end + describe 'when handling lfs batch request on a read-only GitLab instance' do + let(:authorization) { authorize_user } + let(:project) { create(:project) } + let(:path) { "#{project.http_url_to_repo}/info/lfs/objects/batch" } + let(:body) do + { 'objects' => [{ 'oid' => sample_oid, 'size' => sample_size }] } + end + + before do + allow(Gitlab::Database).to receive(:read_only?) { true } + project.team << [user, :master] + enable_lfs + end + + it 'responds with a 200 message on download' do + post_lfs_json path, body.merge('operation' => 'download'), headers + + expect(response).to have_gitlab_http_status(200) + end + + it 'responds with a 403 message on upload' do + post_lfs_json path, body.merge('operation' => 'upload'), headers + + expect(response).to have_gitlab_http_status(403) + expect(json_response).to include('message' => 'You cannot write to this read-only GitLab instance.') + end + end + describe 'when pushing a lfs object' do before do enable_lfs @@ -836,7 +865,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -846,7 +875,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 401' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end @@ -856,7 +885,7 @@ describe 'Git LFS API and storage' do end it 'does not recognize it as a valid lfs command' do - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end end end @@ -868,7 +897,7 @@ describe 'Git LFS API and storage' do end it 'responds with 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -878,7 +907,7 @@ describe 'Git LFS API and storage' do end it 'responds with 403' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -888,7 +917,7 @@ describe 'Git LFS API and storage' do end it 'does not recognize it as a valid lfs command' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -916,7 +945,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'uses the gitlab-workhorse content type' do @@ -936,7 +965,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'lfs object is linked to the project' do @@ -947,12 +976,12 @@ describe 'Git LFS API and storage' do context 'invalid tempfiles' do it 'rejects slashes in the tempfile name (path traversal' do put_finalize('foo/bar') - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end it 'rejects tempfile names that do not start with the oid' do put_finalize("foo#{sample_oid}") - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -981,7 +1010,7 @@ describe 'Git LFS API and storage' do end it 'responds with 403 (not 404 because the build user can read the project)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -995,7 +1024,7 @@ describe 'Git LFS API and storage' do end it 'responds with 404 (do not leak non-public project existence)' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1008,7 +1037,7 @@ describe 'Git LFS API and storage' do end it 'responds with 404 (do not leak non-public project existence)' do - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end end @@ -1037,7 +1066,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'with location of lfs store and object details' do @@ -1053,7 +1082,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'lfs object is linked to the source project' do @@ -1081,7 +1110,7 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end @@ -1092,7 +1121,7 @@ describe 'Git LFS API and storage' do # I'm not sure what this tests that is different from the previous test it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1101,7 +1130,7 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } it 'responds with 403 (not 404 because project is public)' do - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -1126,7 +1155,7 @@ describe 'Git LFS API and storage' do end it 'responds with status 200' do - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end it 'links the lfs object to the project' do @@ -1173,11 +1202,6 @@ describe 'Git LFS API and storage' do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token) end - def fork_project(project, user, object = nil) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) - Projects::ForkService.new(project, user, {}).execute - end - def post_lfs_json(url, body = nil, headers = nil) post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json')) end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index a927de952d0..0b1f8ce6f6d 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -37,7 +37,7 @@ describe 'OpenID Connect requests' do it 'userinfo response is unauthorized' do request_user_info - expect(response).to have_http_status 403 + expect(response).to have_gitlab_http_status 403 expect(response.body).to be_blank end end diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 6667ce771bd..286d8a884a4 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -99,19 +99,19 @@ describe 'cycle analytics events' do it 'does not list the test events' do get project_cycle_analytics_test_path(project, format: :json) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end it 'does not list the staging events' do get project_cycle_analytics_staging_path(project, format: :json) - expect(response).to have_http_status(:not_found) + expect(response).to have_gitlab_http_status(:not_found) end it 'lists the issue events' do get project_cycle_analytics_issue_path(project, format: :json) - expect(response).to have_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) end end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 39d44245c3f..fb1281a6b42 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -426,18 +426,23 @@ describe 'project routing' do end end - # project_milestones GET /:project_id/milestones(.:format) milestones#index - # POST /:project_id/milestones(.:format) milestones#create - # new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new - # edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit - # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show - # PUT /:project_id/milestones/:id(.:format) milestones#update - # DELETE /:project_id/milestones/:id(.:format) milestones#destroy + # project_milestones GET /:project_id/milestones(.:format) milestones#index + # POST /:project_id/milestones(.:format) milestones#create + # new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new + # edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit + # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show + # PUT /:project_id/milestones/:id(.:format) milestones#update + # DELETE /:project_id/milestones/:id(.:format) milestones#destroy + # promote_project_milestone POST /:project_id/milestones/:id/promote milestones#promote describe Projects::MilestonesController, 'routing' do it_behaves_like 'RESTful project resources' do let(:controller) { 'milestones' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end + + it 'to #promote' do + expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1") + end end # project_labels GET /:project_id/labels(.:format) labels#index diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index a45839b16f5..609481603af 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -135,7 +135,6 @@ end # profile_history GET /profile/history(.:format) profile#history # profile_password PUT /profile/password(.:format) profile#password_update # profile_token GET /profile/token(.:format) profile#token -# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token # profile GET /profile(.:format) profile#show # profile_update PUT /profile/update(.:format) profile#update describe ProfilesController, "routing" do @@ -147,10 +146,6 @@ describe ProfilesController, "routing" do expect(get("/profile/audit_log")).to route_to('profiles#audit_log') end - it "to #reset_private_token" do - expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token') - end - it "to #reset_rss_token" do expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token') end @@ -285,17 +280,15 @@ end describe "Groups", "routing" do let(:name) { 'complex.group-namegit' } - - before do - allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true) - end + let!(:group) { create(:group, name: name) } it "to #show" do expect(get("/groups/#{name}")).to route_to('groups#show', id: name) end it "also supports nested groups" do - expect(get("/#{name}/#{name}")).to route_to('groups#show', id: "#{name}/#{name}") + nested_group = create(:group, parent: group) + expect(get("/#{name}/#{nested_group.name}")).to route_to('groups#show', id: "#{name}/#{nested_group.name}") end it "also display group#show on the short path" do @@ -313,10 +306,6 @@ describe "Groups", "routing" do it "to #members" do expect(get("/groups/#{name}/group_members")).to route_to('groups/group_members#index', group_id: name) end - - it "also display group#show with slash in the path" do - expect(get('/group/subgroup')).to route_to('groups#show', id: 'group/subgroup') - end end describe HealthCheckController, 'routing' do diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb index 388b086ce6a..b1dfcf1b048 100644 --- a/spec/rubocop/cop/migration/datetime_spec.rb +++ b/spec/rubocop/cop/migration/datetime_spec.rb @@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::Datetime do include CopHelper subject(:cop) { described_class.new } + let(:migration_with_datetime) do %q( class Users < ActiveRecord::Migration @@ -22,6 +23,19 @@ describe RuboCop::Cop::Migration::Datetime do ) end + let(:migration_with_timestamp) do + %q( + class Users < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column(:users, :username, :text) + add_column(:users, :last_sign_in, :timestamp) + end + end + ) + end + let(:migration_without_datetime) do %q( class Users < ActiveRecord::Migration @@ -58,6 +72,17 @@ describe RuboCop::Cop::Migration::Datetime do aggregate_failures do expect(cop.offenses.size).to eq(1) expect(cop.offenses.map(&:line)).to eq([7]) + expect(cop.offenses.first.message).to include('datetime') + end + end + + it 'registers an offense when the ":timestamp" data type is used' do + inspect_source(cop, migration_with_timestamp) + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([7]) + expect(cop.offenses.first.message).to include('timestamp') end end @@ -81,6 +106,7 @@ describe RuboCop::Cop::Migration::Datetime do context 'outside of migration' do it 'registers no offense' do inspect_source(cop, migration_with_datetime) + inspect_source(cop, migration_with_timestamp) inspect_source(cop, migration_without_datetime) inspect_source(cop, migration_with_datetime_with_timezone) diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb new file mode 100644 index 00000000000..4e859b6f6fa --- /dev/null +++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/rspec/env_assignment' + +describe RuboCop::Cop::RSpec::EnvAssignment do + include CopHelper + + OFFENSE_CALL_SINGLE_QUOTES_KEY = %(ENV['FOO'] = 'bar').freeze + OFFENSE_CALL_DOUBLE_QUOTES_KEY = %(ENV["FOO"] = 'bar').freeze + + let(:source_file) { 'spec/foo_spec.rb' } + + subject(:cop) { described_class.new } + + shared_examples 'an offensive ENV#[]= call' do |content| + it "registers an offense for `#{content}`" do + inspect_source(cop, content, source_file) + + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + expect(cop.highlights).to eq([content]) + end + end + + shared_examples 'an autocorrected ENV#[]= call' do |content, autocorrected_content| + it "registers an offense for `#{content}` and autocorrects it to `#{autocorrected_content}`" do + autocorrected = autocorrect_source(cop, content, source_file) + + expect(autocorrected).to eql(autocorrected_content) + end + end + + context 'in a spec file' do + before do + allow(cop).to receive(:in_spec?).and_return(true) + end + + context 'with a key using single quotes' do + it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY + it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar')) + end + + context 'with a key using double quotes' do + it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY + it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar')) + end + end + + context 'outside of a spec file' do + it "does not register an offense for `#{OFFENSE_CALL_SINGLE_QUOTES_KEY}` in a non-spec file" do + inspect_source(cop, OFFENSE_CALL_SINGLE_QUOTES_KEY) + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb b/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb new file mode 100644 index 00000000000..278662d32ea --- /dev/null +++ b/spec/rubocop/cop/rspec/verbose_include_metadata_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../../rubocop/cop/rspec/verbose_include_metadata' + +describe RuboCop::Cop::RSpec::VerboseIncludeMetadata do + include CopHelper + + subject(:cop) { described_class.new } + + let(:source_file) { 'foo_spec.rb' } + + # Override `CopHelper#inspect_source` to always appear to be in a spec file, + # so that our RSpec-only cop actually runs + def inspect_source(*args) + super(*args, source_file) + end + + shared_examples 'examples with include syntax' do |title| + it "flags violation for #{title} examples that uses verbose include syntax" do + inspect_source(cop, "#{title} 'Test', js: true do; end") + + expect(cop.offenses.size).to eq(1) + offense = cop.offenses.first + + expect(offense.line).to eq(1) + expect(cop.highlights).to eq(["#{title} 'Test', js: true"]) + expect(offense.message).to eq('Use `:js` instead of `js: true`.') + end + + it "doesn't flag violation for #{title} examples that uses compact include syntax", :aggregate_failures do + inspect_source(cop, "#{title} 'Test', :js do; end") + + expect(cop.offenses).to be_empty + end + + it "doesn't flag violation for #{title} examples that uses flag: symbol" do + inspect_source(cop, "#{title} 'Test', flag: :symbol do; end") + + expect(cop.offenses).to be_empty + end + + it "autocorrects #{title} examples that uses verbose syntax into compact syntax" do + autocorrected = autocorrect_source(cop, "#{title} 'Test', js: true do; end", source_file) + + expect(autocorrected).to eql("#{title} 'Test', :js do; end") + end + end + + %w(describe context feature example_group it specify example scenario its).each do |example| + it_behaves_like 'examples with include syntax', example + end +end diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 5b7822d5d8e..f6bd6e9ede4 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe BuildDetailsEntity do + include ProjectForksHelper + set(:user) { create(:admin) } it 'inherits from JobEntity' do @@ -56,18 +58,16 @@ describe BuildDetailsEntity do end context 'when merge request is from a fork' do - let(:fork_project) do - create(:project, forked_from_project: project) - end + let(:forked_project) { fork_project(project) } - let(:pipeline) { create(:ci_pipeline, project: fork_project) } + let(:pipeline) { create(:ci_pipeline, project: forked_project) } before do allow(build).to receive(:merge_request).and_return(merge_request) end let(:merge_request) do - create(:merge_request, source_project: fork_project, + create(:merge_request, source_project: forked_project, target_project: project, source_branch: build.ref) end diff --git a/spec/serializers/build_serializer_spec.rb b/spec/serializers/build_serializer_spec.rb index 01e2cfed6f8..9673b11c2a2 100644 --- a/spec/serializers/build_serializer_spec.rb +++ b/spec/serializers/build_serializer_spec.rb @@ -38,7 +38,7 @@ describe BuildSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") + expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") end end end diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb new file mode 100644 index 00000000000..2c7f49974f1 --- /dev/null +++ b/spec/serializers/cluster_entity_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe ClusterEntity do + set(:cluster) { create(:gcp_cluster, :errored) } + let(:request) { double('request') } + + let(:entity) do + described_class.new(cluster) + end + + describe '#as_json' do + subject { entity.as_json } + + it 'contains status' do + expect(subject[:status]).to eq(:errored) + end + + it 'contains status reason' do + expect(subject[:status_reason]).to eq('general error') + end + end +end diff --git a/spec/serializers/cluster_serializer_spec.rb b/spec/serializers/cluster_serializer_spec.rb new file mode 100644 index 00000000000..1ac6784d28f --- /dev/null +++ b/spec/serializers/cluster_serializer_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe ClusterSerializer do + let(:serializer) do + described_class.new + end + + describe '#represent_status' do + subject { serializer.represent_status(resource) } + + context 'when represents only status' do + let(:resource) { create(:gcp_cluster, :errored) } + + it 'serializes only status' do + expect(subject.keys).to contain_exactly(:status, :status_reason) + end + end + end +end diff --git a/spec/serializers/container_repository_entity_spec.rb b/spec/serializers/container_repository_entity_spec.rb new file mode 100644 index 00000000000..c589cd18f77 --- /dev/null +++ b/spec/serializers/container_repository_entity_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe ContainerRepositoryEntity do + let(:entity) do + described_class.new(repository, request: request) + end + + set(:project) { create(:project) } + set(:user) { create(:user) } + set(:repository) { create(:container_repository, project: project) } + + let(:request) { double('request') } + + subject { entity.as_json } + + before do + stub_container_registry_config(enabled: true) + allow(request).to receive(:project).and_return(project) + allow(request).to receive(:current_user).and_return(user) + end + + it 'exposes required informations' do + expect(subject).to include(:id, :path, :location, :tags_path) + end + + context 'when user can manage repositories' do + before do + project.add_developer(user) + end + + it 'exposes destroy_path' do + expect(subject).to include(:destroy_path) + end + end + + context 'when user cannot manage repositories' do + it 'does not expose destroy_path' do + expect(subject).not_to include(:destroy_path) + end + end +end diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb new file mode 100644 index 00000000000..4beb50c70f8 --- /dev/null +++ b/spec/serializers/container_tag_entity_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe ContainerTagEntity do + let(:entity) do + described_class.new(tag, request: request) + end + + set(:project) { create(:project) } + set(:user) { create(:user) } + set(:repository) { create(:container_repository, name: 'image', project: project) } + + let(:request) { double('request') } + let(:tag) { repository.tag('test') } + + subject { entity.as_json } + + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w[test]) + allow(request).to receive(:project).and_return(project) + allow(request).to receive(:current_user).and_return(user) + end + + it 'exposes required informations' do + expect(subject).to include(:name, :location, :revision, :short_revision, :total_size, :created_at) + end + + context 'when user can manage repositories' do + before do + project.add_developer(user) + end + + it 'exposes destroy_path' do + expect(subject).to include(:destroy_path) + end + end + + context 'when user cannot manage repositories' do + it 'does not expose destroy_path' do + expect(subject).not_to include(:destroy_path) + end + end +end diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb new file mode 100644 index 00000000000..452754d7a79 --- /dev/null +++ b/spec/serializers/group_child_entity_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe GroupChildEntity do + include Gitlab::Routing.url_helpers + + let(:user) { create(:user) } + let(:request) { double('request') } + let(:entity) { described_class.new(object, request: request) } + subject(:json) { entity.as_json } + + before do + allow(request).to receive(:current_user).and_return(user) + end + + shared_examples 'group child json' do + it 'renders json' do + is_expected.not_to be_nil + end + + %w[id + full_name + avatar_url + name + description + visibility + type + can_edit + visibility + permission + relative_path].each do |attribute| + it "includes #{attribute}" do + expect(json[attribute.to_sym]).to be_present + end + end + end + + describe 'for a project' do + let(:object) do + create(:project, :with_avatar, + description: 'Awesomeness') + end + + before do + object.add_master(user) + end + + it 'has the correct type' do + expect(json[:type]).to eq('project') + end + + it 'includes the star count' do + expect(json[:star_count]).to be_present + end + + it 'has the correct edit path' do + expect(json[:edit_path]).to eq(edit_project_path(object)) + end + + it_behaves_like 'group child json' + end + + describe 'for a group', :nested_groups do + let(:object) do + create(:group, :nested, :with_avatar, + description: 'Awesomeness') + end + + before do + object.add_owner(user) + end + + it 'has the correct type' do + expect(json[:type]).to eq('group') + end + + it 'counts projects and subgroups as children' do + create(:project, namespace: object) + create(:group, parent: object) + + expect(json[:children_count]).to eq(2) + end + + %w[children_count leave_path parent_id number_projects_with_delimiter number_users_with_delimiter project_count subgroup_count].each do |attribute| + it "includes #{attribute}" do + expect(json[attribute.to_sym]).to be_present + end + end + + it 'allows an owner to leave when there is another one' do + object.add_owner(create(:user)) + + expect(json[:can_leave]).to be_truthy + end + + it 'has the correct edit path' do + expect(json[:edit_path]).to eq(edit_group_path(object)) + end + + it_behaves_like 'group child json' + end +end diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb new file mode 100644 index 00000000000..5541ada3750 --- /dev/null +++ b/spec/serializers/group_child_serializer_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' + +describe GroupChildSerializer do + let(:request) { double('request') } + let(:user) { create(:user) } + subject(:serializer) { described_class.new(current_user: user) } + + describe '#represent' do + context 'for groups' do + it 'can render a single group' do + expect(serializer.represent(build(:group))).to be_kind_of(Hash) + end + + it 'can render a collection of groups' do + expect(serializer.represent(build_list(:group, 2))).to be_kind_of(Array) + end + end + + context 'with a hierarchy', :nested_groups do + let(:parent) { create(:group) } + + subject(:serializer) do + described_class.new(current_user: user).expand_hierarchy(parent) + end + + it 'expands the subgroups' do + subgroup = create(:group, parent: parent) + subsub_group = create(:group, parent: subgroup) + + json = serializer.represent([subgroup, subsub_group]).first + subsub_group_json = json[:children].first + + expect(json[:id]).to eq(subgroup.id) + expect(subsub_group_json).not_to be_nil + expect(subsub_group_json[:id]).to eq(subsub_group.id) + end + + it 'can render a nested tree' do + subgroup1 = create(:group, parent: parent) + subsub_group1 = create(:group, parent: subgroup1) + subgroup2 = create(:group, parent: parent) + + json = serializer.represent([subgroup1, subsub_group1, subgroup1, subgroup2]) + subgroup1_json = json.first + subsub_group1_json = subgroup1_json[:children].first + + expect(json.size).to eq(2) + expect(subgroup1_json[:id]).to eq(subgroup1.id) + expect(subsub_group1_json[:id]).to eq(subsub_group1.id) + end + + context 'without a specified parent' do + subject(:serializer) do + described_class.new(current_user: user).expand_hierarchy + end + + it 'can render a tree' do + subgroup = create(:group, parent: parent) + + json = serializer.represent([parent, subgroup]) + parent_json = json.first + + expect(parent_json[:id]).to eq(parent.id) + expect(parent_json[:children].first[:id]).to eq(subgroup.id) + end + end + end + + context 'for projects' do + it 'can render a single project' do + expect(serializer.represent(build(:project))).to be_kind_of(Hash) + end + + it 'can render a collection of projects' do + expect(serializer.represent(build_list(:project, 2))).to be_kind_of(Array) + end + + context 'with a hierarchy', :nested_groups do + let(:parent) { create(:group) } + + subject(:serializer) do + described_class.new(current_user: user).expand_hierarchy(parent) + end + + it 'can render a nested tree' do + subgroup1 = create(:group, parent: parent) + project1 = create(:project, namespace: subgroup1) + subgroup2 = create(:group, parent: parent) + project2 = create(:project, namespace: subgroup2) + + json = serializer.represent([project1, project2, subgroup1, subgroup2]) + project1_json, project2_json = json.map { |group_json| group_json[:children].first } + + expect(json.size).to eq(2) + expect(project1_json[:id]).to eq(project1.id) + expect(project2_json[:id]).to eq(project2.id) + end + + it 'returns an array when an array of a single instance was given' do + project = create(:project, namespace: parent) + + json = serializer.represent([project]) + + expect(json).to be_kind_of(Array) + expect(json.size).to eq(1) + end + end + end + end +end diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb new file mode 100644 index 00000000000..caa3e41402b --- /dev/null +++ b/spec/serializers/issue_entity_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe IssueEntity do + let(:project) { create(:project) } + let(:resource) { create(:issue, project: project) } + let(:user) { create(:user) } + + let(:request) { double('request', current_user: user) } + + subject { described_class.new(resource, request: request).as_json } + + it 'has Issuable attributes' do + expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id, + :title, :updated_by_id, :created_at, :updated_at, :milestone, :labels) + end + + it 'has time estimation attributes' do + expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent) + end +end diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb new file mode 100644 index 00000000000..75578816e75 --- /dev/null +++ b/spec/serializers/issue_serializer_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe IssueSerializer do + let(:resource) { create(:issue) } + let(:user) { create(:user) } + let(:json_entity) do + described_class.new(current_user: user) + .represent(resource, serializer: serializer) + .with_indifferent_access + end + + context 'non-sidebar issue serialization' do + let(:serializer) { nil } + + it 'matches issue json schema' do + expect(json_entity).to match_schema('entities/issue') + end + end + + context 'sidebar issue serialization' do + let(:serializer) { 'sidebar' } + + it 'matches sidebar issue json schema' do + expect(json_entity).to match_schema('entities/issue_sidebar') + end + end +end diff --git a/spec/serializers/merge_request_basic_serializer_spec.rb b/spec/serializers/merge_request_basic_serializer_spec.rb index 4daf5a59d0c..1fad8e6bc5d 100644 --- a/spec/serializers/merge_request_basic_serializer_spec.rb +++ b/spec/serializers/merge_request_basic_serializer_spec.rb @@ -4,9 +4,13 @@ describe MergeRequestBasicSerializer do let(:resource) { create(:merge_request) } let(:user) { create(:user) } - subject { described_class.new.represent(resource) } + let(:json_entity) do + described_class.new(current_user: user) + .represent(resource, serializer: 'basic') + .with_indifferent_access + end - it 'has important MergeRequest attributes' do - expect(subject).to include(:merge_status) + it 'matches basic merge request json' do + expect(json_entity).to match_schema('entities/merge_request_basic') end end diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index a2fd5b7daae..f9285049c0d 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe MergeRequestEntity do - let(:project) { create :project } + let(:project) { create :project, :repository } let(:resource) { create(:merge_request, source_project: project, target_project: project) } let(:user) { create(:user) } @@ -11,16 +11,6 @@ describe MergeRequestEntity do described_class.new(resource, request: request).as_json end - it 'includes author' do - req = double('request') - - author_payload = UserEntity - .represent(resource.author, request: req) - .as_json - - expect(subject[:author]).to eq(author_payload) - end - it 'includes pipeline' do req = double('request', current_user: user) pipeline = build_stubbed(:ci_pipeline) @@ -40,14 +30,24 @@ describe MergeRequestEntity do :assign_to_closing) end + it 'has Issuable attributes' do + expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id, + :title, :updated_by_id, :created_at, :updated_at, :milestone, :labels) + end + + it 'has time estimation attributes' do + expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent) + end + it 'has important MergeRequest attributes' do - expect(subject).to include(:diff_head_sha, :merge_commit_message, + expect(subject).to include(:state, :deleted_at, :diff_head_sha, :merge_commit_message, :has_conflicts, :has_ci, :merge_path, :conflict_resolution_path, :cancel_merge_when_pipeline_succeeds_path, :create_issue_to_resolve_discussions_path, :source_branch_path, :target_branch_commits_path, - :target_branch_tree_path, :commits_count, :merge_ongoing) + :target_branch_tree_path, :commits_count, :merge_ongoing, + :ff_only_enabled) end it 'has email_patches_path' do diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb index 73fbecc153d..e3abefa6d63 100644 --- a/spec/serializers/merge_request_serializer_spec.rb +++ b/spec/serializers/merge_request_serializer_spec.rb @@ -9,11 +9,11 @@ describe MergeRequestSerializer do end describe '#represent' do - let(:opts) { { basic: basic } } - subject { serializer.represent(merge_request, basic: basic) } + let(:opts) { { serializer: serializer_entity } } + subject { serializer.represent(merge_request, serializer: serializer_entity) } - context 'when basic param is truthy' do - let(:basic) { true } + context 'when passing basic serializer param' do + let(:serializer_entity) { 'basic' } it 'calls super class #represent with correct params' do expect_any_instance_of(BaseSerializer).to receive(:represent) @@ -23,8 +23,8 @@ describe MergeRequestSerializer do end end - context 'when basic param is falsy' do - let(:basic) { false } + context 'when serializer param is falsy' do + let(:serializer_entity) { nil } it 'calls super class #represent with correct params' do expect_any_instance_of(BaseSerializer).to receive(:represent) diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index f8df461bc81..248552d1858 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -108,5 +108,18 @@ describe PipelineEntity do expect(subject[:ref][:path]).to be_nil end end + + context 'when pipeline has a failure reason set' do + let(:pipeline) { create(:ci_empty_pipeline) } + + before do + pipeline.drop!(:config_error) + end + + it 'has a correct failure reason' do + expect(subject[:failure_reason]) + .to eq 'CI/CD YAML configuration error!' + end + end end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 3baf9b1edab..8fc1ceedc34 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -168,7 +168,7 @@ describe PipelineSerializer do expect(subject[:text]).to eq(status.text) expect(subject[:label]).to eq(status.label) expect(subject[:icon]).to eq(status.icon) - expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico") + expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.ico") end end end diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb index 3964b998084..16431ed4188 100644 --- a/spec/serializers/status_entity_spec.rb +++ b/spec/serializers/status_entity_spec.rb @@ -18,12 +18,12 @@ describe StatusEntity do it 'contains status details' do expect(subject).to include :text, :icon, :favicon, :label, :group expect(subject).to include :has_details, :details_path - expect(subject[:favicon]).to eq('/assets/ci_favicons/favicon_status_success.ico') + expect(subject[:favicon]).to match_asset_path('/assets/ci_favicons/favicon_status_success.ico') end it 'contains a dev namespaced favicon if dev env' do allow(Rails.env).to receive(:development?) { true } - expect(entity.as_json[:favicon]).to eq('/assets/ci_favicons/dev/favicon_status_success.ico') + expect(entity.as_json[:favicon]).to match_asset_path('/assets/ci_favicons/dev/favicon_status_success.ico') end end end diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb new file mode 100644 index 00000000000..47a2a9d6403 --- /dev/null +++ b/spec/services/applications/create_service_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe ::Applications::CreateService do + let(:user) { create(:user) } + let(:params) { attributes_for(:application) } + let(:request) { ActionController::TestRequest.new(remote_ip: '127.0.0.1') } + + subject { described_class.new(user, params) } + + it 'creates an application' do + expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1) + end +end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 1c2d0b3e0dc..9128280eb5a 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -43,6 +43,21 @@ describe Auth::ContainerRegistryAuthenticationService do end end + shared_examples 'a browsable' do + let(:access) do + [{ 'type' => 'registry', + 'name' => 'catalog', + 'actions' => ['*'] }] + end + + it_behaves_like 'a valid token' + it_behaves_like 'not a container repository factory' + + it 'has the correct scope' do + expect(payload).to include('access' => access) + end + end + shared_examples 'an accessible' do let(:access) do [{ 'type' => 'repository', @@ -51,7 +66,10 @@ describe Auth::ContainerRegistryAuthenticationService do end it_behaves_like 'a valid token' - it { expect(payload).to include('access' => access) } + + it 'has the correct scope' do + expect(payload).to include('access' => access) + end end shared_examples 'an inaccessible' do @@ -117,6 +135,17 @@ describe Auth::ContainerRegistryAuthenticationService do context 'user authorization' do let(:current_user) { create(:user) } + context 'for registry catalog' do + let(:current_params) do + { scope: "registry:catalog:*" } + end + + context 'disallow browsing for users without Gitlab admin rights' do + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + end + context 'for private project' do let(:project) { create(:project) } @@ -490,6 +519,16 @@ describe Auth::ContainerRegistryAuthenticationService do end end + context 'registry catalog browsing authorized as admin' do + let(:current_user) { create(:user, :admin) } + + let(:current_params) do + { scope: "registry:catalog:*" } + end + + it_behaves_like 'a browsable' + end + context 'unauthorized' do context 'disallow to use scope-less authentication' do it_behaves_like 'a forbidden' @@ -536,5 +575,14 @@ describe Auth::ContainerRegistryAuthenticationService do it_behaves_like 'not a container repository factory' end end + + context 'for registry catalog' do + let(:current_params) do + { scope: "registry:catalog:*" } + end + + it_behaves_like 'a forbidden' + it_behaves_like 'not a container repository factory' + end end end diff --git a/spec/services/ci/create_cluster_service_spec.rb b/spec/services/ci/create_cluster_service_spec.rb new file mode 100644 index 00000000000..6e7398fbffa --- /dev/null +++ b/spec/services/ci/create_cluster_service_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Ci::CreateClusterService do + describe '#execute' do + let(:access_token) { 'xxx' } + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:result) { described_class.new(project, user, params).execute(access_token) } + + context 'when correct params' do + let(:params) do + { + gcp_project_id: 'gcp-project', + gcp_cluster_name: 'test-cluster', + gcp_cluster_zone: 'us-central1-a', + gcp_cluster_size: 1 + } + end + + it 'creates a cluster object' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { result }.to change { Gcp::Cluster.count }.by(1) + expect(result.gcp_project_id).to eq('gcp-project') + expect(result.gcp_cluster_name).to eq('test-cluster') + expect(result.gcp_cluster_zone).to eq('us-central1-a') + expect(result.gcp_cluster_size).to eq(1) + expect(result.gcp_token).to eq(access_token) + end + end + + context 'when invalid params' do + let(:params) do + { + gcp_project_id: 'gcp-project', + gcp_cluster_name: 'test-cluster', + gcp_cluster_zone: 'us-central1-a', + gcp_cluster_size: 'ABC' + } + end + + it 'returns an error' do + expect(ClusterProvisionWorker).not_to receive(:perform_async) + expect { result }.to change { Gcp::Cluster.count }.by(0) + end + end + end +end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 4c2ff08039c..08847183bf4 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::CreatePipelineService do + include ProjectForksHelper + set(:project) { create(:project, :repository) } let(:user) { create(:admin) } let(:ref_name) { 'refs/heads/master' } @@ -82,13 +84,9 @@ describe Ci::CreatePipelineService do end context 'when merge request target project is different from source project' do + let!(:project) { fork_project(target_project, nil, repository: true) } let!(:target_project) { create(:project, :repository) } - let!(:forked_project_link) do - create(:forked_project_link, forked_to_project: project, - forked_from_project: target_project) - end - it 'updates head pipeline for merge request' do merge_request = create(:merge_request, source_branch: 'master', target_branch: "branch_1", @@ -133,6 +131,26 @@ describe Ci::CreatePipelineService do expect(merge_request.reload.head_pipeline).to eq head_pipeline end end + + context 'when pipeline has been skipped' do + before do + allow_any_instance_of(Ci::Pipeline) + .to receive(:git_commit_message) + .and_return('some commit [ci skip]') + end + + it 'updates merge request head pipeline' do + merge_request = create(:merge_request, source_branch: 'master', + target_branch: 'feature', + source_project: project) + + head_pipeline = execute_service + + expect(head_pipeline).to be_skipped + expect(head_pipeline).to be_persisted + expect(merge_request.reload.head_pipeline).to eq head_pipeline + end + end end context 'auto-cancel enabled' do @@ -481,104 +499,4 @@ describe Ci::CreatePipelineService do end end end - - describe '#allowed_to_create?' do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:ref) { 'master' } - - subject do - described_class.new(project, user, ref: ref) - .send(:allowed_to_create?) - end - - context 'when user is a developer' do - before do - project.add_developer(user) - end - - it { is_expected.to be_truthy } - - context 'when the branch is protected' do - let!(:protected_branch) do - create(:protected_branch, project: project, name: ref) - end - - it { is_expected.to be_falsey } - - context 'when developers are allowed to merge' do - let!(:protected_branch) do - create(:protected_branch, - :developers_can_merge, - project: project, - name: ref) - end - - it { is_expected.to be_truthy } - end - end - - context 'when the tag is protected' do - let(:ref) { 'v1.0.0' } - - let!(:protected_tag) do - create(:protected_tag, project: project, name: ref) - end - - it { is_expected.to be_falsey } - - context 'when developers are allowed to create the tag' do - let!(:protected_tag) do - create(:protected_tag, - :developers_can_create, - project: project, - name: ref) - end - - it { is_expected.to be_truthy } - end - end - end - - context 'when user is a master' do - before do - project.add_master(user) - end - - it { is_expected.to be_truthy } - - context 'when the branch is protected' do - let!(:protected_branch) do - create(:protected_branch, project: project, name: ref) - end - - it { is_expected.to be_truthy } - end - - context 'when the tag is protected' do - let(:ref) { 'v1.0.0' } - - let!(:protected_tag) do - create(:protected_tag, project: project, name: ref) - end - - it { is_expected.to be_truthy } - - context 'when no one can create the tag' do - let!(:protected_tag) do - create(:protected_tag, - :no_one_can_create, - project: project, - name: ref) - end - - it { is_expected.to be_falsey } - end - end - end - - context 'when owner cannot create pipeline' do - it { is_expected.to be_falsey } - end - end end diff --git a/spec/services/ci/extract_sections_from_build_trace_service_spec.rb b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb new file mode 100644 index 00000000000..28f2fa7903a --- /dev/null +++ b/spec/services/ci/extract_sections_from_build_trace_service_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Ci::ExtractSectionsFromBuildTraceService, '#execute' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:build) { create(:ci_build, project: project) } + + subject { described_class.new(project, user) } + + shared_examples 'build trace has sections markers' do + before do + build.trace.set(File.read(expand_fixture_path('trace/trace_with_sections'))) + end + + it 'saves the correct extracted sections' do + expect(build.trace_sections).to be_empty + expect(subject.execute(build)).to be(true) + expect(build.trace_sections).not_to be_empty + end + + it "fails if trace_sections isn't empty" do + expect(subject.execute(build)).to be(true) + expect(build.trace_sections).not_to be_empty + + expect(subject.execute(build)).to be(false) + expect(build.trace_sections).not_to be_empty + end + end + + shared_examples 'build trace has no sections markers' do + before do + build.trace.set('no markerts') + end + + it 'extracts no sections' do + expect(build.trace_sections).to be_empty + expect(subject.execute(build)).to be(true) + expect(build.trace_sections).to be_empty + end + end + + context 'when the build has no user' do + it_behaves_like 'build trace has sections markers' + it_behaves_like 'build trace has no sections markers' + end + + context 'when the build has a valid user' do + before do + build.user = user + end + + it_behaves_like 'build trace has sections markers' + it_behaves_like 'build trace has no sections markers' + end +end diff --git a/spec/services/ci/fetch_gcp_operation_service_spec.rb b/spec/services/ci/fetch_gcp_operation_service_spec.rb new file mode 100644 index 00000000000..7792979c5cb --- /dev/null +++ b/spec/services/ci/fetch_gcp_operation_service_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' +require 'google/apis' + +describe Ci::FetchGcpOperationService do + describe '#execute' do + let(:cluster) { create(:gcp_cluster) } + let(:operation) { double } + + context 'when suceeded' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_operations).and_return(operation) + end + + it 'fetch the gcp operaion' do + expect { |b| described_class.new.execute(cluster, &b) } + .to yield_with_args(operation) + end + end + + context 'when raises an error' do + let(:error) { Google::Apis::ServerError.new('a') } + + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_operations).and_raise(error) + end + + it 'sets an error to cluster object' do + expect { |b| described_class.new.execute(cluster, &b) } + .not_to yield_with_args + expect(cluster.reload).to be_errored + end + end + end +end diff --git a/spec/services/ci/fetch_kubernetes_token_service_spec.rb b/spec/services/ci/fetch_kubernetes_token_service_spec.rb new file mode 100644 index 00000000000..1d05c9671a9 --- /dev/null +++ b/spec/services/ci/fetch_kubernetes_token_service_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Ci::FetchKubernetesTokenService do + describe '#execute' do + subject { described_class.new(api_url, ca_pem, username, password).execute } + + let(:api_url) { 'http://111.111.111.111' } + let(:ca_pem) { '' } + let(:username) { 'admin' } + let(:password) { 'xxx' } + + context 'when params correct' do + let(:token) { 'xxx.token.xxx' } + + let(:secrets_json) do + [ + { + 'metadata': { + name: metadata_name + }, + 'data': { + 'token': Base64.encode64(token) + } + } + ] + end + + before do + allow_any_instance_of(Kubeclient::Client) + .to receive(:get_secrets).and_return(secrets_json) + end + + context 'when default-token exists' do + let(:metadata_name) { 'default-token-123' } + + it { is_expected.to eq(token) } + end + + context 'when default-token does not exist' do + let(:metadata_name) { 'another-token-123' } + + it { is_expected.to be_nil } + end + end + + context 'when api_url is nil' do + let(:api_url) { nil } + + it { expect { subject }.to raise_error("Incomplete settings") } + end + + context 'when username is nil' do + let(:username) { nil } + + it { expect { subject }.to raise_error("Incomplete settings") } + end + + context 'when password is nil' do + let(:password) { nil } + + it { expect { subject }.to raise_error("Incomplete settings") } + end + end +end diff --git a/spec/services/ci/finalize_cluster_creation_service_spec.rb b/spec/services/ci/finalize_cluster_creation_service_spec.rb new file mode 100644 index 00000000000..def3709fdb4 --- /dev/null +++ b/spec/services/ci/finalize_cluster_creation_service_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Ci::FinalizeClusterCreationService do + describe '#execute' do + let(:cluster) { create(:gcp_cluster) } + let(:result) { described_class.new.execute(cluster) } + + context 'when suceeded to get cluster from api' do + let(:gke_cluster) { double } + + before do + allow(gke_cluster).to receive(:endpoint).and_return('111.111.111.111') + allow(gke_cluster).to receive(:master_auth).and_return(spy) + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_get).and_return(gke_cluster) + end + + context 'when suceeded to get kubernetes token' do + let(:kubernetes_token) { 'abc' } + + before do + allow_any_instance_of(Ci::FetchKubernetesTokenService) + .to receive(:execute).and_return(kubernetes_token) + end + + it 'executes integration cluster' do + expect_any_instance_of(Ci::IntegrateClusterService).to receive(:execute) + described_class.new.execute(cluster) + end + end + + context 'when failed to get kubernetes token' do + before do + allow_any_instance_of(Ci::FetchKubernetesTokenService) + .to receive(:execute).and_return(nil) + end + + it 'sets an error to cluster object' do + described_class.new.execute(cluster) + + expect(cluster.reload).to be_errored + end + end + end + + context 'when failed to get cluster from api' do + let(:error) { Google::Apis::ServerError.new('a') } + + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_get).and_raise(error) + end + + it 'sets an error to cluster object' do + described_class.new.execute(cluster) + + expect(cluster.reload).to be_errored + end + end + end +end diff --git a/spec/services/ci/integrate_cluster_service_spec.rb b/spec/services/ci/integrate_cluster_service_spec.rb new file mode 100644 index 00000000000..3a79c205bd1 --- /dev/null +++ b/spec/services/ci/integrate_cluster_service_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Ci::IntegrateClusterService do + describe '#execute' do + let(:cluster) { create(:gcp_cluster, :custom_project_namespace) } + let(:endpoint) { '123.123.123.123' } + let(:ca_cert) { 'ca_cert_xxx' } + let(:token) { 'token_xxx' } + let(:username) { 'username_xxx' } + let(:password) { 'password_xxx' } + + before do + described_class + .new.execute(cluster, endpoint, ca_cert, token, username, password) + + cluster.reload + end + + context 'when correct params' do + it 'creates a cluster object' do + expect(cluster.endpoint).to eq(endpoint) + expect(cluster.ca_cert).to eq(ca_cert) + expect(cluster.kubernetes_token).to eq(token) + expect(cluster.username).to eq(username) + expect(cluster.password).to eq(password) + expect(cluster.service.active).to be_truthy + expect(cluster.service.api_url).to eq(cluster.api_url) + expect(cluster.service.ca_pem).to eq(ca_cert) + expect(cluster.service.namespace).to eq(cluster.project_namespace) + expect(cluster.service.token).to eq(token) + end + end + + context 'when invalid params' do + let(:endpoint) { nil } + + it 'sets an error to cluster object' do + expect(cluster).to be_errored + end + end + end +end diff --git a/spec/services/ci/provision_cluster_service_spec.rb b/spec/services/ci/provision_cluster_service_spec.rb new file mode 100644 index 00000000000..5ce5c788314 --- /dev/null +++ b/spec/services/ci/provision_cluster_service_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe Ci::ProvisionClusterService do + describe '#execute' do + let(:cluster) { create(:gcp_cluster) } + let(:operation) { spy } + + shared_examples 'error' do + it 'sets an error to cluster object' do + described_class.new.execute(cluster) + + expect(cluster.reload).to be_errored + end + end + + context 'when suceeded to request provision' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create).and_return(operation) + end + + context 'when operation status is RUNNING' do + before do + allow(operation).to receive(:status).and_return('RUNNING') + end + + context 'when suceeded to parse gcp operation id' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:parse_operation_id).and_return('operation-123') + end + + context 'when cluster status is scheduled' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:parse_operation_id).and_return('operation-123') + end + + it 'schedules a worker for status minitoring' do + expect(WaitForClusterCreationWorker).to receive(:perform_in) + + described_class.new.execute(cluster) + end + end + + context 'when cluster status is creating' do + before do + cluster.make_creating! + end + + it_behaves_like 'error' + end + end + + context 'when failed to parse gcp operation id' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:parse_operation_id).and_return(nil) + end + + it_behaves_like 'error' + end + end + + context 'when operation status is others' do + before do + allow(operation).to receive(:status).and_return('others') + end + + it_behaves_like 'error' + end + end + + context 'when failed to request provision' do + let(:error) { Google::Apis::ServerError.new('a') } + + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create).and_raise(error) + end + + it_behaves_like 'error' + end + end +end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index fbb3213f42b..b61d1cb765e 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -20,7 +20,7 @@ describe Ci::RetryBuildService do erased_at auto_canceled_by].freeze IGNORE_ACCESSORS = - %i[type lock_version target_url base_tags + %i[type lock_version target_url base_tags trace_sections commit_id deployments erased_by_id last_deployment project_id runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason].freeze @@ -160,8 +160,9 @@ describe Ci::RetryBuildService do expect(new_build).to be_created end - it 'does mark old build as retried' do + it 'does mark old build as retried in the database and on the instance' do expect(new_build).to be_latest + expect(build).to be_retried expect(build.reload).to be_retried end end diff --git a/spec/services/ci/update_cluster_service_spec.rb b/spec/services/ci/update_cluster_service_spec.rb new file mode 100644 index 00000000000..a289385b88f --- /dev/null +++ b/spec/services/ci/update_cluster_service_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Ci::UpdateClusterService do + describe '#execute' do + let(:cluster) { create(:gcp_cluster, :created_on_gke, :with_kubernetes_service) } + + before do + described_class.new(cluster.project, cluster.user, params).execute(cluster) + + cluster.reload + end + + context 'when correct params' do + context 'when enabled is true' do + let(:params) { { 'enabled' => 'true' } } + + it 'enables cluster and overwrite kubernetes service' do + expect(cluster.enabled).to be_truthy + expect(cluster.service.active).to be_truthy + expect(cluster.service.api_url).to eq(cluster.api_url) + expect(cluster.service.ca_pem).to eq(cluster.ca_cert) + expect(cluster.service.namespace).to eq(cluster.project_namespace) + expect(cluster.service.token).to eq(cluster.kubernetes_token) + end + end + + context 'when enabled is false' do + let(:params) { { 'enabled' => 'false' } } + + it 'disables cluster and kubernetes service' do + expect(cluster.enabled).to be_falsy + expect(cluster.service.active).to be_falsy + end + end + end + end +end diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb index 03c682ae0d7..5a9eb359ee1 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/delete_merged_branches_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe DeleteMergedBranchesService do + include ProjectForksHelper + subject(:service) { described_class.new(project, project.owner) } let(:project) { create(:project, :repository) } @@ -50,9 +52,9 @@ describe DeleteMergedBranchesService do context 'open merge requests' do it 'does not delete branches from open merge requests' do - fork_link = create(:forked_project_link, forked_from_project: project) + forked_project = fork_project(project) create(:merge_request, :opened, source_project: project, target_project: project, source_branch: 'branch-merged', target_branch: 'master') - create(:merge_request, :opened, source_project: fork_link.forked_to_project, target_project: project, target_branch: 'improve/awesome', source_branch: 'master') + create(:merge_request, :opened, source_project: forked_project, target_project: project, target_branch: 'improve/awesome', source_branch: 'master') service.execute diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb index 82b156f5ebe..2b84206318f 100644 --- a/spec/services/discussions/update_diff_position_service_spec.rb +++ b/spec/services/discussions/update_diff_position_service_spec.rb @@ -164,8 +164,8 @@ describe Discussions::UpdateDiffPositionService do change_position = discussion.change_position expect(change_position.start_sha).to eq(old_diff_refs.head_sha) expect(change_position.head_sha).to eq(new_diff_refs.head_sha) - expect(change_position.old_line).to eq(9) - expect(change_position.new_line).to be_nil + expect(change_position.formatter.old_line).to eq(9) + expect(change_position.formatter.new_line).to be_nil end it 'creates a system discussion' do @@ -184,7 +184,7 @@ describe Discussions::UpdateDiffPositionService do expect(discussion.original_position).to eq(old_position) expect(discussion.position).not_to eq(old_position) - expect(discussion.position.new_line).to eq(22) + expect(discussion.position.formatter.new_line).to eq(22) end context 'when the resolve_outdated_diff_discussions setting is set' do diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb new file mode 100644 index 00000000000..2b2c31e2521 --- /dev/null +++ b/spec/services/emails/confirm_service_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Emails::ConfirmService do + let(:user) { create(:user) } + + subject(:service) { described_class.new(user) } + + describe '#execute' do + it 'sends a confirmation email again' do + email = user.emails.create(email: 'new@email.com') + mail = service.execute(email) + expect(mail.subject).to eq('Confirmation instructions') + end + end +end diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb index 641d5538de8..54692c88623 100644 --- a/spec/services/emails/create_service_spec.rb +++ b/spec/services/emails/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Emails::CreateService do let(:user) { create(:user) } - let(:opts) { { email: 'new@email.com' } } + let(:opts) { { email: 'new@email.com', user: user } } subject(:service) { described_class.new(user, opts) } @@ -12,6 +12,11 @@ describe Emails::CreateService do expect(Email.where(opts)).not_to be_empty end + it 'creates an email with additional attributes' do + expect { service.execute(confirmation_token: 'abc') }.to change { Email.count }.by(1) + expect(Email.where(opts).first.confirmation_token).to eq 'abc' + end + it 'has the right user association' do service.execute diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb index 1f4294dd905..c3204fac3df 100644 --- a/spec/services/emails/destroy_service_spec.rb +++ b/spec/services/emails/destroy_service_spec.rb @@ -4,11 +4,11 @@ describe Emails::DestroyService do let!(:user) { create(:user) } let!(:email) { create(:email, user: user) } - subject(:service) { described_class.new(user, email: email.email) } + subject(:service) { described_class.new(user, user: user) } describe '#execute' do it 'removes an email' do - expect { service.execute }.to change { user.emails.count }.by(-1) + expect { service.execute(email) }.to change { user.emails.count }.by(-1) end end end diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb new file mode 100644 index 00000000000..b4a4a44d07b --- /dev/null +++ b/spec/services/events/render_service_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Events::RenderService do + describe '#execute' do + let!(:note) { build(:note) } + let!(:event) { build(:event, target: note, project: note.project) } + let!(:user) { build(:user) } + + context 'when the request format is atom' do + it 'renders the note inside events' do + expect(Banzai::ObjectRenderer).to receive(:new) + .with(event.project, user, + only_path: false, + xhtml: true) + .and_call_original + + expect_any_instance_of(Banzai::ObjectRenderer) + .to receive(:render).with([note], :note) + + described_class.new(user).execute([event], atom_request: true) + end + end + + context 'when the request format is not atom' do + it 'renders the note inside events' do + expect(Banzai::ObjectRenderer).to receive(:new) + .with(event.project, user, {}) + .and_call_original + + expect_any_instance_of(Banzai::ObjectRenderer) + .to receive(:render).with([note], :note) + + described_class.new(user).execute([event], atom_request: false) + end + end + end +end diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb index 20382a3a618..1cd2625531e 100644 --- a/spec/services/gpg_keys/create_service_spec.rb +++ b/spec/services/gpg_keys/create_service_spec.rb @@ -18,4 +18,14 @@ describe GpgKeys::CreateService do it 'creates a gpg key' do expect { subject.execute }.to change { user.gpg_keys.where(params).count }.by(1) end + + context 'when the public key contains subkeys' do + let(:params) { attributes_for(:gpg_key_with_subkeys) } + + it 'generates the gpg subkeys' do + gpg_key = subject.execute + + expect(gpg_key.subkeys.count).to eq(2) + end + end end diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb new file mode 100644 index 00000000000..9f92b662be1 --- /dev/null +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Issuable::CommonSystemNotesService do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:issuable) { create(:issue) } + + shared_examples 'system note creation' do |update_params, note_text| + subject { described_class.new(project, user).execute(issuable, [])} + + before do + issuable.assign_attributes(update_params) + issuable.save + end + + it 'creates 1 system note with the correct content' do + expect { subject }.to change { Note.count }.from(0).to(1) + + note = Note.last + expect(note.note).to match(note_text) + expect(note.noteable_type).to eq('Issue') + end + end + + describe '#execute' do + it_behaves_like 'system note creation', { title: 'New title' }, 'changed title' + it_behaves_like 'system note creation', { description: 'New description' }, 'changed the description' + it_behaves_like 'system note creation', { discussion_locked: true }, 'locked this issue' + it_behaves_like 'system note creation', { time_estimate: 5 }, 'changed time estimate' + + context 'when new label is added' do + before do + label = create(:label, project: project) + issuable.labels << label + end + + it_behaves_like 'system note creation', {}, /added ~\w+ label/ + end + + context 'when new milestone is assigned' do + before do + milestone = create(:milestone, project: project) + issuable.milestone_id = milestone.id + end + + it_behaves_like 'system note creation', {}, 'changed milestone' + end + end +end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a8a8aeed1bd..f07b81e842a 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -48,7 +48,8 @@ describe Issues::UpdateService, :mailer do assignee_ids: [user2.id], state_event: 'close', label_ids: [label.id], - due_date: Date.tomorrow + due_date: Date.tomorrow, + discussion_locked: true } end @@ -62,6 +63,7 @@ describe Issues::UpdateService, :mailer do expect(issue).to be_closed expect(issue.labels).to match_array [label] expect(issue.due_date).to eq Date.tomorrow + expect(issue.discussion_locked).to be_truthy end it 'refreshes the number of open issues when the issue is made confidential', :use_clean_rails_memory_store_caching do @@ -110,6 +112,7 @@ describe Issues::UpdateService, :mailer do expect(issue.labels).to be_empty expect(issue.milestone).to be_nil expect(issue.due_date).to be_nil + expect(issue.discussion_locked).to be_falsey end end @@ -148,6 +151,13 @@ describe Issues::UpdateService, :mailer do expect(note).not_to be_nil expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**' end + + it 'creates system note about discussion lock' do + note = find_note('locked this issue') + + expect(note).not_to be_nil + expect(note.note).to eq 'locked this issue' + end end end @@ -257,6 +267,30 @@ describe Issues::UpdateService, :mailer do end end + context 'when a new assignee added' do + subject { update_issue(assignees: issue.assignees + [user2]) } + + it 'creates only 1 new todo' do + expect { subject }.to change { Todo.count }.by(1) + end + + it 'creates a todo for new assignee' do + subject + + attributes = { + project: project, + author: user, + user: user2, + target_id: issue.id, + target_type: issue.class.name, + action: Todo::ASSIGNED, + state: :pending + } + + expect(Todo.where(attributes).count).to eq(1) + end + end + context 'when the milestone change' do it 'marks todos as done' do update_issue(milestone: create(:milestone)) diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 23982b9e6e1..0b32c51a16f 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -35,7 +35,7 @@ describe MergeRequests::Conflicts::ListService do it 'returns a falsey value when the MR has a missing ref after a force push' do merge_request = create_merge_request('conflict-resolvable') service = conflicts_service(merge_request) - allow(service.conflicts).to receive(:merge_index).and_raise(Rugged::OdbError) + allow_any_instance_of(Rugged::Repository).to receive(:merge_commits).and_raise(Rugged::OdbError) expect(service.can_be_resolved_in_ui?).to be_falsey end diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb index 6f49a65d795..5376083e7f5 100644 --- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb +++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb @@ -1,14 +1,12 @@ require 'spec_helper' describe MergeRequests::Conflicts::ResolveService do + include ProjectForksHelper let(:user) { create(:user) } - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :public, :repository) } - let(:fork_project) do - create(:forked_project_with_submodules) do |fork_project| - fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) - fork_project.save - end + let(:forked_project) do + fork_project_with_submodules(project, user) end let(:merge_request) do @@ -19,7 +17,7 @@ describe MergeRequests::Conflicts::ResolveService do let(:merge_request_from_fork) do create(:merge_request, - source_branch: 'conflict-resolvable-fork', source_project: fork_project, + source_branch: 'conflict-resolvable-fork', source_project: forked_project, target_branch: 'conflict-start', target_project: project) end @@ -109,25 +107,27 @@ describe MergeRequests::Conflicts::ResolveService do branch_name: 'conflict-start') end - def resolve_conflicts + subject do described_class.new(merge_request_from_fork).execute(user, params) end it 'gets conflicts from the source project' do - expect(fork_project.repository.rugged).to receive(:merge_commits).and_call_original - expect(project.repository.rugged).not_to receive(:merge_commits) + # REFACTOR NOTE: We used to test that `project.repository.rugged` wasn't + # used in this case, but since the refactor, for simplification, + # we always use that repository for read only operations. + expect(forked_project.repository.rugged).to receive(:merge_commits).and_call_original - resolve_conflicts + subject end it 'creates a commit with the message' do - resolve_conflicts + subject expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message]) end it 'creates a commit with the correct parents' do - resolve_conflicts + subject expect(merge_request_from_fork.source_branch_head.parents.map(&:id)) .to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', target_head]) @@ -202,14 +202,19 @@ describe MergeRequests::Conflicts::ResolveService do } end - it 'raises a MissingResolution error' do + it 'raises a ResolutionError error' do expect { service.execute(user, invalid_params) } - .to raise_error(Gitlab::Conflict::File::MissingResolution) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) end end context 'when the content of a file is unchanged' do - let(:list_service) { MergeRequests::Conflicts::ListService.new(merge_request) } + let(:resolver) do + MergeRequests::Conflicts::ListService.new(merge_request).conflicts.resolver + end + let(:regex_conflict) do + resolver.conflict_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb') + end let(:invalid_params) do { @@ -221,16 +226,16 @@ describe MergeRequests::Conflicts::ResolveService do }, { old_path: 'files/ruby/regex.rb', new_path: 'files/ruby/regex.rb', - content: list_service.conflicts.file_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb').content + content: regex_conflict.content } ], commit_message: 'This is a commit message!' } end - it 'raises a MissingResolution error' do + it 'raises a ResolutionError error' do expect { service.execute(user, invalid_params) } - .to raise_error(Gitlab::Conflict::File::MissingResolution) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) end end @@ -248,9 +253,9 @@ describe MergeRequests::Conflicts::ResolveService do } end - it 'raises a MissingFiles error' do + it 'raises a ResolutionError error' do expect { service.execute(user, invalid_params) } - .to raise_error(described_class::MissingFiles) + .to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError) end end end diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb new file mode 100644 index 00000000000..aaabf3ed2b0 --- /dev/null +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe MergeRequests::FfMergeService do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:merge_request) do + create(:merge_request, + source_branch: 'flatten-dir', + target_branch: 'improve/awesome', + assignee: user2) + end + let(:project) { merge_request.project } + + before do + project.team << [user, :master] + project.team << [user2, :developer] + end + + describe '#execute' do + context 'valid params' do + let(:service) { described_class.new(project, user, {}) } + + before do + allow(service).to receive(:execute_hooks) + + perform_enqueued_jobs do + service.execute(merge_request) + end + end + + it "does not create merge commit" do + source_branch_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha + target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha + expect(source_branch_sha).to eq(target_branch_sha) + end + + it { expect(merge_request).to be_valid } + it { expect(merge_request).to be_merged } + + it 'sends email to user2 about merge of new merge_request' do + email = ActionMailer::Base.deliveries.last + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) + end + + it 'creates system note about merge_request merge' do + note = merge_request.notes.last + expect(note.note).to include 'merged' + end + end + + context "error handling" do + let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } + + before do + allow(Rails.logger).to receive(:error) + end + + it 'logs and saves error if there is an exception' do + error_message = 'error message' + + allow(service).to receive(:repository).and_raise("error message") + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + + expect(merge_request.merge_error).to include(error_message) + expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + end + + it 'logs and saves error if there is an PreReceiveError exception' do + error_message = 'error message' + + allow(service).to receive(:repository).and_raise(Gitlab::Git::HooksService::PreReceiveError, error_message) + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + + expect(merge_request.merge_error).to include(error_message) + expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + end + end + end +end diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index 25599dea19f..274624aa8bb 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -1,6 +1,8 @@ require "spec_helper" describe MergeRequests::GetUrlsService do + include ProjectForksHelper + let(:project) { create(:project, :public, :repository) } let(:service) { described_class.new(project) } let(:source_branch) { "merge-test" } @@ -85,7 +87,7 @@ describe MergeRequests::GetUrlsService do context 'pushing to existing branch from forked project' do let(:user) { create(:user) } - let!(:forked_project) { Projects::ForkService.new(project, user).execute } + let!(:forked_project) { fork_project(project, user, repository: true) } let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) } let(:changes) { existing_branch_changes } # Source project is now the forked one diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index b60136064b7..ac196e92601 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -12,38 +12,6 @@ describe MergeRequests::MergeService do end describe '#execute' do - context 'MergeRequest#merge_jid' do - before do - merge_request.update_column(:merge_jid, 'hash-123') - end - - it 'is cleaned when no error is raised' do - service = described_class.new(project, user, commit_message: 'Awesome message') - - service.execute(merge_request) - - expect(merge_request.reload.merge_jid).to be_nil - end - - it 'is cleaned when expected error is raised' do - service = described_class.new(project, user, commit_message: 'Awesome message') - allow(service).to receive(:commit).and_raise(described_class::MergeError) - - service.execute(merge_request) - - expect(merge_request.reload.merge_jid).to be_nil - end - - it 'is not cleaned when unexpected error is raised' do - service = described_class.new(project, user, commit_message: 'Awesome message') - allow(service).to receive(:commit).and_raise(StandardError) - - expect { service.execute(merge_request) }.to raise_error(StandardError) - - expect(merge_request.reload.merge_jid).to be_present - end - end - context 'valid params' do let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } @@ -168,7 +136,7 @@ describe MergeRequests::MergeService do context 'source branch removal' do context 'when the source branch is protected' do let(:service) do - described_class.new(project, user, should_remove_source_branch: '1') + described_class.new(project, user, 'should_remove_source_branch' => true) end before do @@ -183,7 +151,7 @@ describe MergeRequests::MergeService do context 'when the source branch is the default branch' do let(:service) do - described_class.new(project, user, should_remove_source_branch: '1') + described_class.new(project, user, 'should_remove_source_branch' => true) end before do @@ -198,10 +166,10 @@ describe MergeRequests::MergeService do context 'when the source branch can be removed' do context 'when MR author set the source branch to be removed' do - let(:service) do - merge_request.merge_params['force_remove_source_branch'] = '1' - merge_request.save! - described_class.new(project, user, commit_message: 'Awesome message') + let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } + + before do + merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' }) end it 'removes the source branch using the author user' do @@ -210,11 +178,20 @@ describe MergeRequests::MergeService do .and_call_original service.execute(merge_request) end + + context 'when the merger set the source branch not to be removed' do + let(:service) { described_class.new(project, user, commit_message: 'Awesome message', 'should_remove_source_branch' => false) } + + it 'does not delete the source branch' do + expect(DeleteBranchService).not_to receive(:new) + service.execute(merge_request) + end + end end context 'when MR merger set the source branch to be removed' do let(:service) do - described_class.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1') + described_class.new(project, user, commit_message: 'Awesome message', 'should_remove_source_branch' => true) end it 'removes the source branch using the current user' do diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index a37cdab8928..d2bd05d921f 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -11,5 +11,16 @@ describe MergeRequests::PostMergeService do describe '#execute' do it_behaves_like 'cache counters invalidator' + + it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do + # Cache the counter before the MR changed state. + project.open_merge_requests_count + merge_request.update!(state: 'merged') + + service = described_class.new(project, user, {}) + + expect { service.execute(merge_request) } + .to change { project.open_merge_requests_count }.from(1).to(0) + end end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 64e676f22a0..a2c05761f6b 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequests::RefreshService do + include ProjectForksHelper + let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { described_class } @@ -12,7 +14,8 @@ describe MergeRequests::RefreshService do group.add_owner(@user) @project = create(:project, :repository, namespace: group) - @fork_project = Projects::ForkService.new(@project, @user).execute + @fork_project = fork_project(@project, @user, repository: true) + @merge_request = create(:merge_request, source_project: @project, source_branch: 'master', @@ -58,7 +61,7 @@ describe MergeRequests::RefreshService do it 'executes hooks with update action' do expect(refresh_service).to have_received(:execute_hooks) - .with(@merge_request, 'update', @oldrev) + .with(@merge_request, 'update', old_rev: @oldrev) expect(@merge_request.notes).not_to be_empty expect(@merge_request).to be_open @@ -84,7 +87,7 @@ describe MergeRequests::RefreshService do it 'executes hooks with update action' do expect(refresh_service).to have_received(:execute_hooks) - .with(@merge_request, 'update', @oldrev) + .with(@merge_request, 'update', old_rev: @oldrev) expect(@merge_request.notes).not_to be_empty expect(@merge_request).to be_open @@ -179,7 +182,7 @@ describe MergeRequests::RefreshService do it 'executes hooks with update action' do expect(refresh_service).to have_received(:execute_hooks) - .with(@fork_merge_request, 'update', @oldrev) + .with(@fork_merge_request, 'update', old_rev: @oldrev) expect(@merge_request.notes).to be_empty expect(@merge_request).to be_open @@ -261,7 +264,7 @@ describe MergeRequests::RefreshService do it 'refreshes the merge request' do expect(refresh_service).to receive(:execute_hooks) - .with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA) + .with(@fork_merge_request, 'update', old_rev: Gitlab::Git::BLANK_SHA) allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev) refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master') @@ -311,8 +314,7 @@ describe MergeRequests::RefreshService do context 'when the merge request is sourced from a different project' do it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do - forked_project = create(:project, :repository) - create(:forked_project_link, forked_to_project: forked_project, forked_from_project: @project) + forked_project = fork_project(@project, @user, repository: true) merge_request = create(:merge_request, target_branch: 'master', diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 681feee61d1..98409be4236 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -49,7 +49,8 @@ describe MergeRequests::UpdateService, :mailer do state_event: 'close', label_ids: [label.id], target_branch: 'target', - force_remove_source_branch: '1' + force_remove_source_branch: '1', + discussion_locked: true } end @@ -73,11 +74,13 @@ describe MergeRequests::UpdateService, :mailer do expect(@merge_request.labels.first.title).to eq(label.name) expect(@merge_request.target_branch).to eq('target') expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') + expect(@merge_request.discussion_locked).to be_truthy end it 'executes hooks with update action' do - expect(service).to have_received(:execute_hooks) - .with(@merge_request, 'update') + expect(service) + .to have_received(:execute_hooks) + .with(@merge_request, 'update', old_labels: [], old_assignees: [user3]) end it 'sends email to user2 about assign of new merge request and email to user3 about merge request unassignment' do @@ -123,6 +126,13 @@ describe MergeRequests::UpdateService, :mailer do expect(note.note).to eq 'changed target branch from `master` to `target`' end + it 'creates system note about discussion lock' do + note = find_note('locked this merge request') + + expect(note).not_to be_nil + expect(note.note).to eq 'locked this merge request' + end + context 'when not including source branch removal options' do before do opts.delete(:force_remove_source_branch) diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb new file mode 100644 index 00000000000..9f2df6d6d19 --- /dev/null +++ b/spec/services/milestones/promote_service_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Milestones::PromoteService do + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + let(:user) { create(:user) } + let(:milestone_title) { 'project milestone' } + let(:milestone) { create(:milestone, project: project, title: milestone_title) } + let(:service) { described_class.new(project, user) } + + describe '#execute' do + before do + group.add_master(user) + end + + context 'validations' do + it 'raises error if milestone does not belong to a project' do + allow(milestone).to receive(:project_milestone?).and_return(false) + + expect { service.execute(milestone) }.to raise_error(described_class::PromoteMilestoneError) + end + + it 'raises error if project does not belong to a group' do + project.update(namespace: user.namespace) + + expect { service.execute(milestone) }.to raise_error(described_class::PromoteMilestoneError) + end + end + + context 'without duplicated milestone titles across projects' do + it 'promotes project milestone to group milestone' do + promoted_milestone = service.execute(milestone) + + expect(promoted_milestone).to be_group_milestone + end + + it 'sets issuables with new promoted milestone' do + issue = create(:issue, milestone: milestone, project: project) + merge_request = create(:merge_request, milestone: milestone, source_project: project) + + promoted_milestone = service.execute(milestone) + + expect(promoted_milestone).to be_group_milestone + expect(issue.reload.milestone).to eq(promoted_milestone) + expect(merge_request.reload.milestone).to eq(promoted_milestone) + end + end + + context 'with duplicated milestone titles across projects' do + let(:project_2) { create(:project, namespace: group) } + let!(:milestone_2) { create(:milestone, project: project_2, title: milestone_title) } + + it 'deletes project milestones with the same title' do + promoted_milestone = service.execute(milestone) + + expect(promoted_milestone).to be_group_milestone + expect(promoted_milestone).to be_valid + expect(Milestone.exists?(milestone.id)).to be_falsy + expect(Milestone.exists?(milestone_2.id)).to be_falsy + end + + it 'sets all issuables with new promoted milestone' do + issue = create(:issue, milestone: milestone, project: project) + issue_2 = create(:issue, milestone: milestone_2, project: project_2) + merge_request = create(:merge_request, milestone: milestone, source_project: project) + merge_request_2 = create(:merge_request, milestone: milestone_2, source_project: project_2) + + promoted_milestone = service.execute(milestone) + + expect(issue.reload.milestone).to eq(promoted_milestone) + expect(issue_2.reload.milestone).to eq(promoted_milestone) + expect(merge_request.reload.milestone).to eq(promoted_milestone) + expect(merge_request_2.reload.milestone).to eq(promoted_milestone) + end + end + end +end diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb new file mode 100644 index 00000000000..faac498037f --- /dev/null +++ b/spec/services/notes/render_service_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Notes::RenderService do + describe '#execute' do + it 'renders a Note' do + note = double(:note) + project = double(:project) + wiki = double(:wiki) + user = double(:user) + + expect(Banzai::ObjectRenderer).to receive(:new) + .with(project, user, + requested_path: 'foo', + project_wiki: wiki, + ref: 'bar', + only_path: nil, + xhtml: false) + .and_call_original + + expect_any_instance_of(Banzai::ObjectRenderer) + .to receive(:render).with([note], :note) + + described_class.new(user).execute([note], project, + requested_path: 'foo', + project_wiki: wiki, + ref: 'bar', + only_path: nil, + xhtml: false) + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index f4b36eb7eeb..b13e12e7c94 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -105,18 +105,6 @@ describe NotificationService, :mailer do end end - describe 'Email' do - describe '#new_email' do - let!(:email) { create(:email) } - - it { expect(notification.new_email(email)).to be_truthy } - - it 'sends email to email owner' do - expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1) - end - end - end - describe 'Notes' do context 'issue note' do let(:project) { create(:project, :private) } @@ -743,6 +731,18 @@ describe NotificationService, :mailer do should_not_email(@u_participating) end + it "doesn't send multiple email when a user is subscribed to multiple given labels" do + subscriber_to_both = create(:user) do |user| + [label_1, label_2].each { |label| label.toggle_subscription(user, project) } + end + + notification.relabeled_issue(issue, [label_1, label_2], @u_disabled) + + should_email(subscriber_to_label_1) + should_email(subscriber_to_label_2) + should_email(subscriber_to_both) + end + context 'confidential issues' do let(:author) { create(:user) } let(:assignee) { create(:user) } diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 5da634e2fb1..dc89fdebce7 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -76,9 +76,8 @@ describe Projects::CreateService, '#execute' do context 'wiki_enabled true creates wiki repository directory' do it do project = create_project(user, opts) - path = ProjectWiki.new(project, user).send(:path_to_repo) - expect(File.exist?(path)).to be_truthy + expect(wiki_repo(project).exists?).to be_truthy end end @@ -86,11 +85,15 @@ describe Projects::CreateService, '#execute' do it do opts[:wiki_enabled] = false project = create_project(user, opts) - path = ProjectWiki.new(project, user).send(:path_to_repo) - expect(File.exist?(path)).to be_falsey + expect(wiki_repo(project).exists?).to be_falsey end end + + def wiki_repo(project) + relative_path = ProjectWiki.new(project).disk_path + '.git' + Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar') + end end context 'builds_enabled global setting' do @@ -149,6 +152,9 @@ describe Projects::CreateService, '#execute' do end context 'when another repository already exists on disk' do + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:opts) do { name: 'Existing', @@ -156,30 +162,59 @@ describe Projects::CreateService, '#execute' do } end - let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] } + context 'with legacy storage' do + before do + gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") + end - before do - gitlab_shell.add_repository(repository_storage_path, "#{user.namespace.full_path}/existing") - end + after do + gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + end - after do - gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") - end + it 'does not allow to create a project when path matches existing repository on disk' do + project = create_project(user, opts) - it 'does not allow to create project with same path' do - project = create_project(user, opts) + expect(project).not_to be_persisted + expect(project).to respond_to(:errors) + expect(project.errors.messages).to have_key(:base) + expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') + end - expect(project).to respond_to(:errors) - expect(project.errors.messages).to have_key(:base) - expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') + it 'does not allow to import project when path matches existing repository on disk' do + project = create_project(user, opts.merge({ import_url: 'https://gitlab.com/gitlab-org/gitlab-test.git' })) + + expect(project).not_to be_persisted + expect(project).to respond_to(:errors) + expect(project.errors.messages).to have_key(:base) + expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') + end end - it 'does not allow to import a project with the same path' do - project = create_project(user, opts.merge({ import_url: 'https://gitlab.com/gitlab-org/gitlab-test.git' })) + context 'with hashed storage' do + let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + let(:hashed_path) { '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + + before do + stub_application_setting(hashed_storage_enabled: true) + allow(Digest::SHA2).to receive(:hexdigest) { hash } + end - expect(project).to respond_to(:errors) - expect(project.errors.messages).to have_key(:base) - expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') + before do + gitlab_shell.add_repository(repository_storage, hashed_path) + end + + after do + gitlab_shell.remove_repository(repository_storage_path, hashed_path) + end + + it 'does not allow to create a project when path matches existing repository on disk' do + project = create_project(user, opts) + + expect(project).not_to be_persisted + expect(project).to respond_to(:errors) + expect(project.errors.messages).to have_key(:base) + expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk') + end end end end @@ -208,6 +243,15 @@ describe Projects::CreateService, '#execute' do end end + context 'when skip_disk_validation is used' do + it 'sets the project attribute' do + opts[:skip_disk_validation] = true + project = create_project(user, opts) + + expect(project.skip_disk_validation).to be_truthy + end + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index c867139d1de..0bec2054f50 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::DestroyService do + include ProjectForksHelper + let!(:user) { create(:user) } let!(:project) { create(:project, :repository, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } @@ -212,6 +214,34 @@ describe Projects::DestroyService do end end + context 'for a forked project with LFS objects' do + let(:forked_project) { fork_project(project, user) } + + before do + project.lfs_objects << create(:lfs_object) + forked_project.forked_project_link.destroy + forked_project.reload + end + + it 'destroys the fork' do + expect { destroy_project(forked_project, user) } + .not_to raise_error + end + end + + context 'as the root of a fork network' do + let!(:fork_network) { create(:fork_network, root_project: project) } + + it 'updates the fork network with the project name' do + destroy_project(project, user) + + fork_network.reload + + expect(fork_network.deleted_root_project_name).to eq(project.full_name) + expect(fork_network.root_project).to be_nil + end + end + def destroy_project(project, user, params = {}) if async Projects::DestroyService.new(project, user, params).async_execute diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index a6e0364d44c..53862283a27 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Projects::ForkService do + include ProjectForksHelper let(:gitlab_shell) { Gitlab::Shell.new } describe 'fork by user' do @@ -33,7 +34,7 @@ describe Projects::ForkService do end describe "successfully creates project in the user namespace" do - let(:to_project) { fork_project(@from_project, @to_user) } + let(:to_project) { fork_project(@from_project, @to_user, namespace: @to_user.namespace) } it { expect(to_project).to be_persisted } it { expect(to_project.errors).to be_empty } @@ -60,13 +61,40 @@ describe Projects::ForkService do expect(@from_project.forks_count).to eq(1) end + + it 'creates a fork network with the new project and the root project set' do + to_project + fork_network = @from_project.reload.fork_network + + expect(fork_network).not_to be_nil + expect(fork_network.root_project).to eq(@from_project) + expect(fork_network.projects).to contain_exactly(@from_project, to_project) + end + end + + context 'creating a fork of a fork' do + let(:from_forked_project) { fork_project(@from_project, @to_user) } + let(:other_namespace) do + group = create(:group) + group.add_owner(@to_user) + group + end + let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) } + + it 'sets the root of the network to the root project' do + expect(to_project.fork_network.root_project).to eq(@from_project) + end + + it 'sets the forked_from_project on the membership' do + expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project) + end end end context 'project already exists' do it "fails due to validation, not transaction failure" do @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) - @to_project = fork_project(@from_project, @to_user) + @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace) expect(@existing_project).to be_persisted expect(@to_project).not_to be_persisted @@ -76,10 +104,11 @@ describe Projects::ForkService do end context 'repository already exists' do - let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] } + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } before do - gitlab_shell.add_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}") + gitlab_shell.add_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") end after do @@ -87,7 +116,7 @@ describe Projects::ForkService do end it 'does not allow creation' do - to_project = fork_project(@from_project, @to_user) + to_project = fork_project(@from_project, @to_user, namespace: @to_user.namespace) expect(to_project).not_to be_persisted expect(to_project.errors.messages).to have_key(:base) @@ -181,9 +210,4 @@ describe Projects::ForkService do end end end - - def fork_project(from_project, user, params = {}) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) - Projects::ForkService.new(from_project, user, params).execute - end end diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb new file mode 100644 index 00000000000..ffb270d277e --- /dev/null +++ b/spec/services/projects/group_links/create_service_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Projects::GroupLinks::CreateService, '#execute' do + let(:user) { create :user } + let(:group) { create :group } + let(:project) { create :project } + let(:opts) do + { + link_group_access: '30', + expires_at: nil + } + end + let(:subject) { described_class.new(project, user, opts) } + + it 'adds group to project' do + expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1) + end + + it 'returns false if group is blank' do + expect { subject.execute(nil) }.not_to change { project.project_group_links.count } + end +end diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb new file mode 100644 index 00000000000..336ee01ae50 --- /dev/null +++ b/spec/services/projects/group_links/destroy_service_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Projects::GroupLinks::DestroyService, '#execute' do + let(:group_link) { create :project_group_link } + let(:project) { group_link.project } + let(:user) { create :user } + let(:subject) { described_class.new(project, user) } + + it 'removes group from project' do + expect { subject.execute(group_link) }.to change { project.project_group_links.count }.from(1).to(0) + end + + it 'returns false if group_link is blank' do + expect { subject.execute(nil) }.not_to change { project.project_group_links.count } + end +end diff --git a/spec/services/projects/hashed_storage_migration_service_spec.rb b/spec/services/projects/hashed_storage_migration_service_spec.rb new file mode 100644 index 00000000000..b71b47c59b6 --- /dev/null +++ b/spec/services/projects/hashed_storage_migration_service_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe Projects::HashedStorageMigrationService do + let(:gitlab_shell) { Gitlab::Shell.new } + let(:project) { create(:project, :empty_repo, :wiki_repo) } + let(:service) { described_class.new(project) } + let(:legacy_storage) { Storage::LegacyProject.new(project) } + let(:hashed_storage) { Storage::HashedProject.new(project) } + + describe '#execute' do + before do + allow(service).to receive(:gitlab_shell) { gitlab_shell } + end + + context 'when succeeds' do + it 'renames project and wiki repositories' do + service.execute + + expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.wiki.git")).to be_truthy + end + + it 'updates project to be hashed and not read-only' do + service.execute + + expect(project.hashed_storage?(:repository)).to be_truthy + expect(project.repository_read_only).to be_falsey + end + + it 'move operation is called for both repositories' do + expect_move_repository(project.disk_path, hashed_storage.disk_path) + expect_move_repository("#{project.disk_path}.wiki", "#{hashed_storage.disk_path}.wiki") + + service.execute + end + end + + context 'when one move fails' do + it 'rollsback repositories to original name' do + from_name = project.disk_path + to_name = hashed_storage.disk_path + allow(service).to receive(:move_repository).and_call_original + allow(service).to receive(:move_repository).with(from_name, to_name).once { false } # will disable first move only + + expect(service).to receive(:rollback_folder_move).and_call_original + + service.execute + + expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.wiki.git")).to be_falsey + end + + context 'when rollback fails' do + before do + from_name = legacy_storage.disk_path + to_name = hashed_storage.disk_path + + hashed_storage.ensure_storage_path_exists + gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) + end + + it 'does not try to move nil repository over hashed' do + expect_move_repository("#{project.disk_path}.wiki", "#{hashed_storage.disk_path}.wiki") + + service.execute + end + end + end + + def expect_move_repository(from_name, to_name) + expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage_path, from_name, to_name).and_call_original + end + end +end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index a14ed526f68..2459f371a91 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -121,11 +121,14 @@ describe Projects::TransferService do end context 'namespace which contains orphan repository with same projects path name' do - let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] } + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } before do group.add_owner(user) - gitlab_shell.add_repository(repository_storage_path, "#{group.full_path}/#{project.path}") + unless gitlab_shell.add_repository(repository_storage, "#{group.full_path}/#{project.path}") + raise 'failed to add repository' + end @result = transfer_project(project, user, group) end diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 4f1ab697460..2bba71fef4f 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -1,37 +1,59 @@ require 'spec_helper' describe Projects::UnlinkForkService do - subject { described_class.new(fork_project, user) } + include ProjectForksHelper - let(:fork_link) { create(:forked_project_link) } - let(:fork_project) { fork_link.forked_to_project } + subject { described_class.new(forked_project, user) } + + let(:fork_link) { forked_project.forked_project_link } + let(:project) { create(:project, :public) } + let(:forked_project) { fork_project(project, user) } let(:user) { create(:user) } context 'with opened merge request on the source project' do - let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) } - let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) } + let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) } + let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) } + let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) } + + let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) } before do allow(MergeRequests::CloseService).to receive(:new) - .with(fork_project, user) + .with(forked_project, user) .and_return(mr_close_service) end it 'close all pending merge requests' do expect(mr_close_service).to receive(:execute).with(merge_request) + expect(mr_close_service).to receive(:execute).with(merge_request2) subject.execute end + + it 'does not close merge requests for the project being unlinked' do + expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork) + end end it 'remove fork relation' do - expect(fork_project.forked_project_link).to receive(:destroy) + expect(forked_project.forked_project_link).to receive(:destroy) subject.execute end + it 'removes the link to the fork network' do + expect(forked_project.fork_network_member).to be_present + expect(forked_project.fork_network).to be_present + + subject.execute + forked_project.reload + + expect(forked_project.fork_network_member).to be_nil + expect(forked_project.reload.fork_network).to be_nil + end + it 'refreshes the forks count cache of the source project' do - source = fork_project.forked_from_project + source = forked_project.forked_from_project expect(source.forks_count).to eq(1) @@ -39,4 +61,14 @@ describe Projects::UnlinkForkService do expect(source.forks_count).to be_zero end + + context 'when the original project was deleted' do + it 'does not fail when the original project is deleted' do + source = forked_project.forked_from_project + source.destroy + forked_project.reload + + expect { subject.execute }.not_to raise_error + end + end end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 031366d1825..d4ac1f6ad81 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -52,6 +52,11 @@ describe Projects::UpdatePagesService do expect(project.pages_deployed?).to be_falsey expect(execute).to eq(:success) expect(project.pages_deployed?).to be_truthy + + # Check that all expected files are extracted + %w[index.html zero .hidden/file].each do |filename| + expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy + end end it 'limits pages size' do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index c551083ac90..3da222e2ed8 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::UpdateService, '#execute' do + include ProjectForksHelper + let(:gitlab_shell) { Gitlab::Shell.new } let(:user) { create(:user) } let(:admin) { create(:admin) } @@ -76,13 +78,7 @@ describe Projects::UpdateService, '#execute' do describe 'when updating project that has forks' do let(:project) { create(:project, :internal) } - let(:forked_project) { create(:forked_project_with_submodules, :internal) } - - before do - forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, - forked_from_project_id: project.id) - forked_project.save - end + let(:forked_project) { fork_project(project) } it 'updates forks visibility level when parent set to more restrictive' do opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } @@ -149,24 +145,43 @@ describe Projects::UpdateService, '#execute' do end context 'when renaming a project' do - let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] } + let(:repository_storage) { 'default' } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } - before do - gitlab_shell.add_repository(repository_storage_path, "#{user.namespace.full_path}/existing") - end + context 'with legacy storage' do + before do + gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") + end + + after do + gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + end - after do - gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + it 'does not allow renaming when new path matches existing repository on disk' do + result = update_project(project, admin, path: 'existing') + + expect(result).to include(status: :error) + expect(result[:message]).to match('There is already a repository with that name on disk') + expect(project).not_to be_valid + expect(project.errors.messages).to have_key(:base) + expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') + end end - it 'does not allow renaming when new path matches existing repository on disk' do - result = update_project(project, admin, path: 'existing') + context 'with hashed storage' do + let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } - expect(result).to include(status: :error) - expect(result[:message]).to match('There is already a repository with that name on disk') - expect(project).not_to be_valid - expect(project.errors.messages).to have_key(:base) - expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') + before do + stub_application_setting(hashed_storage_enabled: true) + end + + it 'does not check if new path matches existing repository on disk' do + expect(project).not_to receive(:repository_with_same_path_already_exists?) + + result = update_project(project, admin, path: 'existing') + + expect(result).to include(status: :success) + end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 6926ac85de3..c35177f6ebc 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -207,7 +207,11 @@ describe QuickActions::InterpretService do it 'populates spend_time: 3600 if content contains /spend 1h' do _, updates = service.execute(content, issuable) - expect(updates).to eq(spend_time: { duration: 3600, user: developer }) + expect(updates).to eq(spend_time: { + duration: 3600, + user: developer, + spent_at: DateTime.now.to_date + }) end end @@ -215,7 +219,39 @@ describe QuickActions::InterpretService do it 'populates spend_time: -1800 if content contains /spend -30m' do _, updates = service.execute(content, issuable) - expect(updates).to eq(spend_time: { duration: -1800, user: developer }) + expect(updates).to eq(spend_time: { + duration: -1800, + user: developer, + spent_at: DateTime.now.to_date + }) + end + end + + shared_examples 'spend command with valid date' do + it 'populates spend time: 1800 with date in date type format' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(spend_time: { + duration: 1800, + user: developer, + spent_at: Date.parse(date) + }) + end + end + + shared_examples 'spend command with invalid date' do + it 'will not create any note and timelog' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq({}) + end + end + + shared_examples 'spend command with future date' do + it 'will not create any note and timelog' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq({}) end end @@ -669,6 +705,22 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end + it_behaves_like 'spend command with valid date' do + let(:date) { '2016-02-02' } + let(:content) { "/spend 30m #{date}" } + let(:issuable) { issue } + end + + it_behaves_like 'spend command with invalid date' do + let(:content) { '/spend 30m 17-99-99' } + let(:issuable) { issue } + end + + it_behaves_like 'spend command with future date' do + let(:content) { '/spend 30m 6017-10-10' } + let(:issuable) { issue } + end + it_behaves_like 'empty command' do let(:content) { '/spend' } let(:issuable) { issue } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 8b5d9187785..46cd10cdc12 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -63,11 +63,54 @@ describe SystemHooksService do :group_id, :user_id, :user_username, :user_name, :user_email, :group_access ) end + + it 'includes the correct project visibility level' do + data = event_data(project, :create) + + expect(data[:project_visibility]).to eq('private') + end + + context 'group_rename' do + it 'contains old and new path' do + allow(group).to receive(:path_was).and_return('old-path') + + data = event_data(group, :rename) + + expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path) + expect(data[:path]).to eq(group.path) + expect(data[:full_path]).to eq(group.path) + expect(data[:old_path]).to eq(group.path_was) + expect(data[:old_full_path]).to eq(group.path_was) + end + + it 'contains old and new full_path for subgroup' do + subgroup = create(:group, parent: group) + allow(subgroup).to receive(:path_was).and_return('old-path') + + data = event_data(subgroup, :rename) + + expect(data[:full_path]).to eq(subgroup.full_path) + expect(data[:old_path]).to eq('old-path') + end + end + + context 'user_rename' do + it 'contains old and new username' do + allow(user).to receive(:username_was).and_return('old-username') + + data = event_data(user, :rename) + + expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username) + expect(data[:username]).to eq(user.username) + expect(data[:old_username]).to eq(user.username_was) + end + end end context 'event names' do it { expect(event_name(user, :create)).to eq "user_create" } it { expect(event_name(user, :destroy)).to eq "user_destroy" } + it { expect(event_name(user, :rename)).to eq 'user_rename' } it { expect(event_name(project, :create)).to eq "project_create" } it { expect(event_name(project, :destroy)).to eq "project_destroy" } it { expect(event_name(project, :rename)).to eq "project_rename" } @@ -79,6 +122,7 @@ describe SystemHooksService do it { expect(event_name(key, :destroy)).to eq 'key_destroy' } it { expect(event_name(group, :create)).to eq 'group_create' } it { expect(event_name(group, :destroy)).to eq 'group_destroy' } + it { expect(event_name(group, :rename)).to eq 'group_rename' } it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' } it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' } end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index b1241cd8d0b..0a6ab455abe 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -502,20 +502,6 @@ describe SystemNoteService do end end - describe '.cross_reference?' do - it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('mentioned in something')).to be_truthy - end - - it 'is truthy when text begins with legacy capitalized expected text' do - expect(described_class.cross_reference?('mentioned in something')).to be_truthy - end - - it 'is falsey when text does not begin with expected text' do - expect(described_class.cross_reference?('this is a note')).to be_falsey - end - end - describe '.cross_reference_disallowed?' do context 'when mentioner is not a MergeRequest' do it 'is falsey' do @@ -1145,4 +1131,42 @@ describe SystemNoteService do it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue" } end end + + describe '.discussion_lock' do + subject { described_class.discussion_lock(noteable, author) } + + context 'discussion unlocked' do + it_behaves_like 'a system note' do + let(:action) { 'unlocked' } + end + + it 'creates the note text correctly' do + [:issue, :merge_request].each do |type| + issuable = create(type) + + expect(described_class.discussion_lock(issuable, author).note) + .to eq("unlocked this #{type.to_s.titleize.downcase}") + end + end + end + + context 'discussion locked' do + before do + noteable.update_attribute(:discussion_locked, true) + end + + it_behaves_like 'a system note' do + let(:action) { 'locked' } + end + + it 'creates the note text correctly' do + [:issue, :merge_request].each do |type| + issuable = create(type, discussion_locked: true) + + expect(described_class.discussion_lock(issuable, author).note) + .to eq("locked this #{type.to_s.titleize.downcase}") + end + end + end + end end diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index 57013b54560..e7e9080b6b0 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -28,7 +28,7 @@ describe Tags::CreateService do it 'returns an error' do expect(repository).to receive(:add_tag) .with(user, 'v1.1.0', 'master', 'Foo') - .and_raise(Rugged::TagError) + .and_raise(Gitlab::Git::Repository::TagExistsError) response = service.execute('v1.1.0', 'master', 'Foo') diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index a9b34a5258a..dc2673abc73 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -248,11 +248,11 @@ describe TodoService do end end - describe '#destroy_issue' do + describe '#destroy_issuable' do it 'refresh the todos count cache for the user' do expect(john_doe).to receive(:update_todos_count_cache).and_call_original - service.destroy_issue(issue, john_doe) + service.destroy_issuable(issue, john_doe) end end @@ -643,14 +643,6 @@ describe TodoService do end end - describe '#destroy_merge_request' do - it 'refresh the todos count cache for the user' do - expect(john_doe).to receive(:update_todos_count_cache).and_call_original - - service.destroy_merge_request(mr_assigned, john_doe) - end - end - describe '#reassigned_merge_request' do it 'creates a pending todo for new assignee' do mr_unassigned.update_attribute(:assignee, john_doe) diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index fef4da0c76e..17eabad73be 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -38,6 +38,18 @@ describe Users::ActivityService do end end end + + context 'when in GitLab read-only instance' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end + + it 'does not update last_activity_at' do + service.execute + + expect(last_hour_user_ids).to eq([]) + end + end end def last_hour_user_ids diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb index 956358738fe..2b6c0267a0f 100644 --- a/spec/services/users/last_push_event_service_spec.rb +++ b/spec/services/users/last_push_event_service_spec.rb @@ -22,7 +22,6 @@ describe Users::LastPushEventService do it 'caches the event for the origin project when pushing to a fork' do source = build(:project, id: 5) - allow(project).to receive(:forked?).and_return(true) allow(project).to receive(:forked_from_project).and_return(source) expect(service).to receive(:set_key) diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index 6ee35a33b2d..f8d4a47b212 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -31,13 +31,13 @@ describe Users::UpdateService do end def update_user(user, opts) - described_class.new(user, opts).execute + described_class.new(user, opts.merge(user: user)).execute end end describe '#execute!' do it 'updates the name' do - service = described_class.new(user, name: 'New Name') + service = described_class.new(user, user: user, name: 'New Name') expect(service).not_to receive(:notify_new_user) result = service.execute! @@ -55,7 +55,7 @@ describe Users::UpdateService do it 'fires system hooks when a new user is saved' do system_hook_service = spy(:system_hook_service) user = build(:user) - service = described_class.new(user, name: 'John Doe') + service = described_class.new(user, user: user, name: 'John Doe') expect(service).to receive(:notify_new_user).and_call_original expect(service).to receive(:system_hook_service).and_return(system_hook_service) @@ -65,7 +65,7 @@ describe Users::UpdateService do end def update_user(user, opts) - described_class.new(user, opts).execute! + described_class.new(user, opts.merge(user: user)).execute! end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 92735336572..7c8331f6c60 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -48,7 +48,11 @@ RSpec.configure do |config| config.include Warden::Test::Helpers, type: :request config.include LoginHelpers, type: :feature config.include SearchHelpers, type: :feature + config.include CookieHelper, :js + config.include InputHelper, :js + config.include InspectRequests, :js config.include WaitForRequests, :js + config.include LiveDebugger, :js config.include StubConfiguration config.include EmailHelpers, :mailer, type: :mailer config.include TestEnv @@ -64,8 +68,16 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! - config.define_derived_metadata(file_path: %r{/spec/requests/(ci/)?api/}) do |metadata| - metadata[:api] = true + config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| + location = metadata[:location] + + metadata[:api] = true if location =~ %r{/spec/requests/api/} + + # do not overwrite type if it's already set + next if metadata.key?(:type) + + match = location.match(%r{/spec/([^/]+)/}) + metadata[:type] = match[1].singularize.to_sym if match end config.raise_errors_for_deprecations! @@ -73,7 +85,10 @@ RSpec.configure do |config| if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. config.default_retry_count = 4 - config.reporter.register_listener(RspecFlaky::Listener.new, :example_passed, :dump_summary) + config.reporter.register_listener( + RspecFlaky::Listener.new, + :example_passed, + :dump_summary) end config.before(:suite) do @@ -161,6 +176,24 @@ RSpec.configure do |config| end end +# add simpler way to match asset paths containing digest strings +RSpec::Matchers.define :match_asset_path do |expected| + match do |actual| + path = Regexp.escape(expected) + extname = Regexp.escape(File.extname(expected)) + digest_regex = Regexp.new(path.sub(extname, "(?:-\\h+)?#{extname}") << '$') + digest_regex =~ actual + end + + failure_message do |actual| + "expected that #{actual} would include an asset path for #{expected}" + end + + failure_message_when_negated do |actual| + "expected that #{actual} would not include an asset path for #{expected}" + end +end + FactoryGirl::SyntaxRunner.class_eval do include RSpec::Mocks::ExampleMethods end diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb index d26d279363c..d2d6260dfa8 100644 --- a/spec/support/api/issues_resolving_discussions_shared_examples.rb +++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb @@ -1,6 +1,6 @@ shared_examples 'creating an issue resolving discussions through the API' do it 'creates a new project issue' do - expect(response).to have_http_status(:created) + expect(response).to have_gitlab_http_status(:created) end it 'resolves the discussions in a merge request' do diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb index dab71a35a55..8d910e52eda 100644 --- a/spec/support/api/members_shared_examples.rb +++ b/spec/support/api/members_shared_examples.rb @@ -6,6 +6,6 @@ shared_examples 'a 404 response when source is private' do it 'returns 404' do route - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 4bb5113957e..d9080b02541 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -10,7 +10,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns milestones list' do get api(route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) @@ -19,13 +19,13 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a 401 error if user not authenticated' do get api(route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns an array of active milestones' do get api("#{route}/?state=active", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -35,7 +35,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns an array of closed milestones' do get api("#{route}/?state=closed", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.length).to eq(1) @@ -47,7 +47,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(route, user), iids: [closed_milestone.iid, other_milestone.iid] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(2) expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) @@ -56,7 +56,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'does not return any milestone if none found' do get api(route, user), iids: [Milestone.maximum(:iid).succ] - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.length).to eq(0) end @@ -75,7 +75,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by searching for title' do get api(route, user), search: 'version2' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title @@ -85,7 +85,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestones by searching for description' do get api(route, user), search: 'open' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq milestone.title @@ -97,7 +97,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by id' do get api(resource_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -105,7 +105,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a milestone by id' do get api(resource_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) end @@ -113,13 +113,13 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns 401 error if user not authenticated' do get api(resource_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns a 404 error if milestone id not found' do get api("#{route}/1234", user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end end @@ -127,7 +127,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'creates a new milestone' do post api(route, user), title: 'new milestone' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil end @@ -136,7 +136,7 @@ shared_examples_for 'group and project milestones' do |route_definition| post api(route, user), title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') expect(json_response['start_date']).to eq('2013-02-02') @@ -145,20 +145,20 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns a 400 error if title is missing' do post api(route, user) - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'returns a 400 error if params are invalid (duplicate title)' do post api(route, user), title: milestone.title, description: 'release', due_date: '2013-03-02' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) end it 'creates a new milestone with reserved html characters' do post api(route, user), title: 'foo & bar 1.1 -> 2.2' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') expect(json_response['description']).to be_nil end @@ -169,7 +169,7 @@ shared_examples_for 'group and project milestones' do |route_definition| put api(resource_route, user), title: 'updated title' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -178,7 +178,7 @@ shared_examples_for 'group and project milestones' do |route_definition| put api(resource_route, user), due_date: nil - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['due_date']).to be_nil end @@ -186,13 +186,13 @@ shared_examples_for 'group and project milestones' do |route_definition| put api("#{route}/1234", user), title: 'updated title' - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'closes milestone' do put api(resource_route, user), state_event: 'close' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['state']).to eq('closed') end @@ -207,7 +207,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns issues for a particular milestone' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) @@ -228,14 +228,14 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'matches V4 response schema for a list of issues' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/issues') end it 'returns a 401 error if user not authenticated' do get api(issues_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end describe 'confidential issues' do @@ -265,7 +265,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'returns confidential issues to team members' do get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) @@ -279,7 +279,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(issues_route, member) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -289,7 +289,7 @@ shared_examples_for 'group and project milestones' do |route_definition| it 'does not return confidential issues to regular users' do get api(issues_route, create(:user)) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -302,7 +302,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(issues_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) @@ -325,7 +325,7 @@ shared_examples_for 'group and project milestones' do |route_definition| another_merge_request get api(merge_requests_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(1) expect(json_response.first['title']).to eq(merge_request.title) @@ -349,20 +349,20 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(not_found_route, user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 404 if the user has no access to the milestone' do new_user = create :user get api(merge_requests_route, new_user) - expect(response).to have_http_status(404) + expect(response).to have_gitlab_http_status(404) end it 'returns a 401 error if user not authenticated' do get api(merge_requests_route) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(401) end it 'returns merge_requests ordered by position asc' do @@ -372,7 +372,7 @@ shared_examples_for 'group and project milestones' do |route_definition| get api(merge_requests_route, user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(2) diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb index 57e28e040d7..06ae8792c61 100644 --- a/spec/support/api/scopes/read_user_shared_examples.rb +++ b/spec/support/api/scopes/read_user_shared_examples.rb @@ -6,7 +6,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -16,7 +16,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -27,10 +27,10 @@ shared_examples_for 'allows the "read_user" scope' do stub_container_registry_config(enabled: true) end - it 'returns a "401" response' do + it 'returns a "403" response' do get api_call.call(path, user, personal_access_token: token) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end @@ -44,7 +44,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -54,7 +54,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "200" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) end end @@ -64,7 +64,7 @@ shared_examples_for 'allows the "read_user" scope' do it 'returns a "403" response' do get api_call.call(path, user, oauth_access_token: token) - expect(response).to have_http_status(403) + expect(response).to have_gitlab_http_status(403) end end end @@ -74,10 +74,10 @@ shared_examples_for 'does not allow the "read_user" scope' do context 'when the requesting token has the "read_user" scope' do let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) } - it 'returns a "401" response' do + it 'returns a "403" response' do post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3) - expect(response).to have_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb index 16a3cf06be7..af1083f4bfd 100644 --- a/spec/support/api/time_tracking_shared_examples.rb +++ b/spec/support/api/time_tracking_shared_examples.rb @@ -15,7 +15,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "sets the time estimate for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['human_time_estimate']).to eq('1w') end @@ -28,7 +28,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it 'does not modify the original estimate' do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(issuable.reload.human_time_estimate).to eq('1w') end end @@ -37,7 +37,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it 'updates the estimate' do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(issuable.reload.human_time_estimate).to eq('3w 1h') end end @@ -54,7 +54,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "resets the time estimate for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_time_estimate", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['time_estimate']).to eq(0) end end @@ -73,7 +73,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '2h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['human_total_time_spent']).to eq('2h') end @@ -84,7 +84,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['total_time_spent']).to eq(3600) end end @@ -96,7 +96,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), duration: '-1w' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/) end end @@ -112,7 +112,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| it "resets spent time for #{issuable_name}" do post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/reset_spent_time", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(0) end end @@ -124,7 +124,7 @@ shared_examples 'time tracking endpoints' do |issuable_name| get api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_stats", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(1800) expect(json_response['time_estimate']).to eq(3600) end diff --git a/spec/support/api/v3/time_tracking_shared_examples.rb b/spec/support/api/v3/time_tracking_shared_examples.rb index f982b10d999..afe0f4cecda 100644 --- a/spec/support/api/v3/time_tracking_shared_examples.rb +++ b/spec/support/api/v3/time_tracking_shared_examples.rb @@ -11,7 +11,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "sets the time estimate for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '1w' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['human_time_estimate']).to eq('1w') end @@ -24,7 +24,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it 'does not modify the original estimate' do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: 'foo' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(issuable.reload.human_time_estimate).to eq('1w') end end @@ -33,7 +33,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it 'updates the estimate' do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_estimate", user), duration: '3w1h' - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(issuable.reload.human_time_estimate).to eq('3w 1h') end end @@ -50,7 +50,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "resets the time estimate for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_time_estimate", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['time_estimate']).to eq(0) end end @@ -69,7 +69,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '2h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['human_total_time_spent']).to eq('2h') end @@ -80,7 +80,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1h' - expect(response).to have_http_status(201) + expect(response).to have_gitlab_http_status(201) expect(json_response['total_time_spent']).to eq(3600) end end @@ -92,7 +92,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/add_spent_time", user), duration: '-1w' - expect(response).to have_http_status(400) + expect(response).to have_gitlab_http_status(400) expect(json_response['message']['time_spent'].first).to match(/exceeds the total time spent/) end end @@ -108,7 +108,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| it "resets spent time for #{issuable_name}" do post v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/reset_spent_time", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(0) end end @@ -120,7 +120,7 @@ shared_examples 'V3 time tracking endpoints' do |issuable_name| get v3_api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.id}/time_stats", user) - expect(response).to have_http_status(200) + expect(response).to have_gitlab_http_status(200) expect(json_response['total_time_spent']).to eq(1800) expect(json_response['time_estimate']).to eq(3600) end diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb index 01aca74274c..ac0c7a9b493 100644 --- a/spec/support/api_helpers.rb +++ b/spec/support/api_helpers.rb @@ -18,21 +18,23 @@ module ApiHelpers # # Returns the relative path to the requested API resource def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil) - "/api/#{version}#{path}" + + full_path = "/api/#{version}#{path}" - # Normalize query string - (path.index('?') ? '' : '?') + + if oauth_access_token + query_string = "access_token=#{oauth_access_token.token}" + elsif personal_access_token + query_string = "private_token=#{personal_access_token.token}" + elsif user + personal_access_token = create(:personal_access_token, user: user) + query_string = "private_token=#{personal_access_token.token}" + end - if personal_access_token.present? - "&private_token=#{personal_access_token.token}" - elsif oauth_access_token.present? - "&access_token=#{oauth_access_token.token}" - # Append private_token if given a User object - elsif user.respond_to?(:private_token) - "&private_token=#{user.private_token}" - else - '' - end + if query_string + full_path << (path.index('?') ? '&' : '?') + full_path << query_string + end + + full_path end # Temporary helper method for simplifying V3 exclusive API specs diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb new file mode 100644 index 00000000000..38d11992dc2 --- /dev/null +++ b/spec/support/bare_repo_operations.rb @@ -0,0 +1,60 @@ +require 'zlib' + +class BareRepoOperations + # The ID of empty tree. + # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012 + EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze + + include Gitlab::Popen + + def initialize(path_to_repo) + @path_to_repo = path_to_repo + end + + # Based on https://stackoverflow.com/a/25556917/1856239 + def commit_file(file, dst_path, branch = 'master') + head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID + + execute(['read-tree', '--empty']) + execute(['read-tree', head_id]) + + blob_id = execute(['hash-object', '--stdin', '-w']) do |stdin| + stdin.write(file.read) + end + + execute(['update-index', '--add', '--cacheinfo', '100644', blob_id[0], dst_path]) + + tree_id = execute(['write-tree']) + + commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"] + commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID + commit_id = execute(commit_tree_args) + + execute(['update-ref', "refs/heads/#{branch}", commit_id[0]]) + end + + private + + def execute(args, allow_failure: false) + output, status = popen(base_args + args, nil) do |stdin| + yield stdin if block_given? + end + + unless status.zero? + if allow_failure + return [] + else + raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" + end + end + + output.split("\n") + end + + def base_args + [ + Gitlab.config.git.bin_path, + "--git-dir=#{@path_to_repo}" + ] + end +end diff --git a/spec/support/board_helpers.rb b/spec/support/board_helpers.rb new file mode 100644 index 00000000000..507d0432d7f --- /dev/null +++ b/spec/support/board_helpers.rb @@ -0,0 +1,16 @@ +module BoardHelpers + def click_card(card) + within card do + first('.card-number').click + end + + wait_for_sidebar + end + + def wait_for_sidebar + # loop until the CSS transition is complete + Timeout.timeout(0.5) do + loop until evaluate_script('$(".right-sidebar").outerWidth()') == 290 + end + end +end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index c45c4a4310d..9f672bc92fc 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,25 +1,25 @@ # rubocop:disable Style/GlobalVars require 'capybara/rails' require 'capybara/rspec' -require 'capybara/poltergeist' require 'capybara-screenshot/rspec' +require 'selenium-webdriver' # Give CI some extra time timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30 -Capybara.javascript_driver = :poltergeist -Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new( - app, - js_errors: true, - timeout: timeout, - window_size: [1366, 768], - url_whitelist: %w[localhost 127.0.0.1], - url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg], - phantomjs_options: [ - '--load-images=yes' - ] +Capybara.javascript_driver = :chrome +Capybara.register_driver :chrome do |app| + extra_args = [] + extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i + + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { + 'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args + } ) + + Capybara::Selenium::Driver + .new(app, browser: :chrome, desired_capabilities: capabilities) end Capybara.default_max_wait_time = timeout @@ -27,6 +27,10 @@ Capybara.ignore_hidden_elements = true # Keep only the screenshots generated from the last failing test suite Capybara::Screenshot.prune_strategy = :keep_last_run +# From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326 +Capybara::Screenshot.register_driver(:chrome) do |driver, path| + driver.browser.save_screenshot(path) +end RSpec.configure do |config| config.before(:context, :js) do @@ -37,13 +41,23 @@ RSpec.configure do |config| end config.before(:example, :js) do + session = Capybara.current_session + allow(Gitlab::Application.routes).to receive(:default_url_options).and_return( - host: Capybara.current_session.server.host, - port: Capybara.current_session.server.port, + host: session.server.host, + port: session.server.port, protocol: 'http') + + # reset window size between tests + unless session.current_window.size == [1240, 1400] + session.current_window.resize_to(1240, 1400) rescue nil + end end config.after(:example, :js) do |example| + # prevent localstorage from introducing side effects based on test order + execute_script("localStorage.clear();") + # capybara/rspec already calls Capybara.reset_sessions! in an `after` hook, # but `block_and_wait_for_requests_complete` is called before it so by # calling it explicitely here, we prevent any new requests from being fired diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb index 3eb7bea3227..868233416bf 100644 --- a/spec/support/capybara_helpers.rb +++ b/spec/support/capybara_helpers.rb @@ -38,7 +38,7 @@ module CapybaraHelpers # Simulate a browser restart by clearing the session cookie. def clear_browser_session - page.driver.remove_cookie('_gitlab_session') + page.driver.browser.manage.delete_cookie('_gitlab_session') end end diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb new file mode 100644 index 00000000000..224619c899c --- /dev/null +++ b/spec/support/cookie_helper.rb @@ -0,0 +1,17 @@ +# Helper for setting cookies in Selenium/WebDriver +# +module CookieHelper + def set_cookie(name, value, options = {}) + # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`. + # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive. + visit options.fetch(:path, '/') unless on_a_page? + page.driver.browser.manage.add_cookie(name: name, value: value, **options) + end + + private + + def on_a_page? + current_url = Capybara.current_session.driver.browser.current_url + current_url && current_url != '' && current_url != 'about:blank' && current_url != 'data:,' + end +end diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb index 3e979f2f470..b39052923dd 100644 --- a/spec/support/email_helpers.rb +++ b/spec/support/email_helpers.rb @@ -1,6 +1,6 @@ module EmailHelpers - def sent_to_user?(user, recipients = email_recipients) - recipients.include?(user.notification_email) + def sent_to_user(user, recipients: email_recipients) + recipients.count { |to| to == user.notification_email } end def reset_delivered_emails! @@ -10,17 +10,17 @@ module EmailHelpers def should_only_email(*users, kind: :to) recipients = email_recipients(kind: kind) - users.each { |user| should_email(user, recipients) } + users.each { |user| should_email(user, recipients: recipients) } expect(recipients.count).to eq(users.count) end - def should_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_truthy + def should_email(user, times: 1, recipients: email_recipients) + expect(sent_to_user(user, recipients: recipients)).to eq(times) end - def should_not_email(user, recipients = email_recipients) - expect(sent_to_user?(user, recipients)).to be_falsey + def should_not_email(user, recipients: email_recipients) + should_email(user, times: 0, recipients: recipients) end def should_not_email_anyone diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 81cb94ab8c4..aabc64d972b 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -77,20 +77,22 @@ shared_examples 'discussion comments' do |resource_name| end it 'clicking the ul padding or divider should not change the text' do - find(menu_selector).trigger 'click' + execute_script("document.querySelector('#{menu_selector}').click()") + # on issues page, the menu closes when clicking anywhere, on other pages it will + # remain open if clicking divider or menu padding, but should not change button action if resource_name == 'issue' expect(find(dropdown_selector)).to have_content 'Comment' find(toggle_selector).click - find("#{menu_selector} .divider").trigger 'click' + execute_script("document.querySelector('#{menu_selector} .divider').click()") else - find(menu_selector).trigger 'click' + execute_script("document.querySelector('#{menu_selector}').click()") expect(page).to have_selector menu_selector expect(find(dropdown_selector)).to have_content 'Comment' - find("#{menu_selector} .divider").trigger 'click' + execute_script("document.querySelector('#{menu_selector} .divider').click()") expect(page).to have_selector menu_selector end @@ -105,7 +107,12 @@ shared_examples 'discussion comments' do |resource_name| end it 'updates the submit button text and closes the dropdown' do - expect(find(dropdown_selector)).to have_content 'Start discussion' + # on issues page, the submit input is a <button>, on other pages it is <input> + if resource_name == 'issue' + expect(find(submit_selector)).to have_content 'Start discussion' + else + expect(find(submit_selector).value).to eq 'Start discussion' + end expect(page).not_to have_selector menu_selector end @@ -121,14 +128,31 @@ shared_examples 'discussion comments' do |resource_name| end end - it 'clicking "Start discussion" will post a discussion' do - find(submit_selector).click + describe 'creating a discussion' do + before do + find(submit_selector).click + find(comments_selector, match: :first) + end + + it 'clicking "Start discussion" will post a discussion' do + new_comment = all(comments_selector).last + + expect(new_comment).to have_content 'a' + expect(new_comment).to have_selector '.discussion' + end + + if resource_name == 'merge request' + it 'shows resolved discussion when toggled' do + click_button "Resolve discussion" + + expect(page).to have_selector('.note-row-1', visible: true) - find(comments_selector, match: :first) - new_comment = all(comments_selector).last + refresh + click_button "Toggle discussion" - expect(new_comment).to have_content 'a' - expect(new_comment).to have_selector '.discussion' + expect(page).to have_selector('.note-row-1', visible: true) + end + end end if resource_name == 'issue' @@ -170,7 +194,12 @@ shared_examples 'discussion comments' do |resource_name| end it 'updates the submit button text and closes the dropdown' do - expect(find(dropdown_selector)).to have_content 'Comment' + # on issues page, the submit input is a <button>, on other pages it is <input> + if resource_name == 'issue' + expect(find(submit_selector)).to have_content 'Comment' + else + expect(find(submit_selector).value).to eq 'Comment' + end expect(page).not_to have_selector menu_selector end @@ -209,6 +238,7 @@ shared_examples 'discussion comments' do |resource_name| describe "on a closed #{resource_name}" do before do find("#{form_selector} .js-note-target-close").click + wait_for_requests find("#{form_selector} .note-textarea").send_keys('a') end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 8282ba7e536..08e21ee2537 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -29,7 +29,7 @@ shared_examples 'issuable record that supports quick actions in its description wait_for_requests end - describe "new #{issuable_type}", js: true do + describe "new #{issuable_type}", :js do context 'with commands in the description' do it "creates the #{issuable_type} and interpret commands accordingly" do case issuable_type @@ -53,7 +53,7 @@ shared_examples 'issuable record that supports quick actions in its description end end - describe "note on #{issuable_type}", js: true do + describe "note on #{issuable_type}", :js do before do visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end @@ -61,7 +61,7 @@ shared_examples 'issuable record that supports quick actions in its description context 'with a note containing commands' do it 'creates a note without the commands and interpret the commands accordingly' do assignee = create(:user, username: 'bob') - write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") + write_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") expect(page).to have_content 'Awesome!' expect(page).not_to have_content '/assign @bob' @@ -82,7 +82,7 @@ shared_examples 'issuable record that supports quick actions in its description context 'with a note containing only commands' do it 'does not create a note but interpret the commands accordingly' do assignee = create(:user, username: 'bob') - write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") + write_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") expect(page).not_to have_content '/assign @bob' expect(page).not_to have_content '/label ~bug' @@ -290,7 +290,7 @@ shared_examples 'issuable record that supports quick actions in its description end end - describe "preview of note on #{issuable_type}", js: true do + describe "preview of note on #{issuable_type}", :js do it 'removes quick actions from note and explains them' do create(:user, username: 'bob') diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 192a2fed0a8..836e5e7be23 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -39,7 +39,7 @@ shared_examples 'reportable note' do |type| end def open_dropdown(dropdown) - dropdown.find('.more-actions-toggle').trigger('click') + dropdown.find('.more-actions-toggle').click dropdown.find('.dropdown-menu li', match: :first) end end diff --git a/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 Binary files differnew file mode 100644 index 00000000000..1c47f34b9a5 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 diff --git a/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf Binary files differnew file mode 100644 index 00000000000..ca13c8df66a --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf diff --git a/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 new file mode 100644 index 00000000000..3be244dbda4 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 @@ -0,0 +1,2 @@ +x¥ŽK +Â0EgNIÒ|ADtè*^’
mZ qGîÄY×àð8—×ZK©ý®7"ÈFc’Ò%oH¢D²Ü9rZÛLÎs“MJ2Œ™=±ÑÒAå…CmeFg²·V¨xI9øH2†¯þXÜJ…ár»pÅ6‡Ï;NÔà8•zˆ??>ß+–ù×z¡¹WÆBÞÎÙf·Ç}«þßb¡N@K\SYîì•iSC
\ No newline at end of file diff --git a/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e Binary files differnew file mode 100644 index 00000000000..2bf27fe5048 --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e diff --git a/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f Binary files differnew file mode 100644 index 00000000000..8ab8606c6be --- /dev/null +++ b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json index 688175369ae..658ff5871b0 100644 --- a/spec/support/gitlab_stubs/session.json +++ b/spec/support/gitlab_stubs/session.json @@ -14,7 +14,5 @@ "provider":null, "is_admin":false, "can_create_group":false, - "can_create_project":false, - "private_token":"Wvjy2Krpb7y8xi93owUz", - "access_token":"Wvjy2Krpb7y8xi93owUz" + "can_create_project":false } diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json index ce8dfe5ae75..658ff5871b0 100644 --- a/spec/support/gitlab_stubs/user.json +++ b/spec/support/gitlab_stubs/user.json @@ -14,7 +14,5 @@ "provider":null, "is_admin":false, "can_create_group":false, - "can_create_project":false, - "private_token":"Wvjy2Krpb7y8xi93owUz", - "access_token":"Wvjy2Krpb7y8xi93owUz" -}
\ No newline at end of file + "can_create_project":false +} diff --git a/spec/support/gpg_helpers.rb b/spec/support/gpg_helpers.rb index 65b38626a51..3f7279a50e0 100644 --- a/spec/support/gpg_helpers.rb +++ b/spec/support/gpg_helpers.rb @@ -92,6 +92,46 @@ module GpgHelpers KEY end + def public_key_with_extra_signing_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + Version: GnuPG v1 + + mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV + 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+ + QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0 + LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4 + BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf + AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa + piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4 + uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB + BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh + Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit + LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF + Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb + 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K + AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT + IKzj0ZyC7DK5AQ0EWcx23AEIANwpAq85bT10JCBuNhOMyF2jKVt5wHbI9wBtjWYG + fgJFBkRvm6IsbmR0Y5DSBvF/of0UX1iGMfx6mvCDJkb1okquhCUef6MONWRpzXYE + CIZDm1TXu6yv0D35tkLfPo+/sY9UHHp1zGRcPAU46e8ztRwoD+zEJwy7lobLHGOL + 9OdWtCGjsutLOTqKRK4jsifr8n3rePU09rejhDkRONNs7ufn9GRcWMN7RWiFDtpU + gNe84AJ38qaXPU8GHNTrDtDtRRPmn68ezMmE1qTNsxQxD4Isexe5Wsfc4+ElaP9s + zaHgij7npX1HS9RpmhnOa2h1ESroM9cqDh3IJVhf+eP6/uMAEQEAAYkBxAQYAQIA + DwUCWcx23AIbAgUJAeEzgAEpCRDM++GfAKyLHcBdIAQZAQIABgUCWcx23AAKCRDk + garE0uOuES7DCAC2Kgl6zO+NqIBIS6McgcEN0sGyvOvZ8Ps4hBiMwCyDAnsIRAUi + v4KZMtQMAyl9njJ3YjPWBsdieuTz45O06DDnrzJpZO5rUGJjAcEue4zvRRWIyu3H + qHC8MsvkslsNCygJHoWlknm+HucroskTNtxHQ+FdKZ6Tey+twl1u+PhV8PQVyFkl + 4G1chO90EP4dvYrye26CC+ik2JkvC7Vy5M+U0PJikme8pFMjcdNks25BnAKcdqKU + AU8RTkSjoYvb8qSmZyldJjYjQRkTPRX1ZdaOID1EdiWl+s5cn0Oypo3z7BChcEMx + IWB/gmXQJQXVr5qNQnJObyMO/RczXYi9qNnyGMED/2EJJERLR0nefjHQalwDKQVP + s5lX1OKAcf2CrV6ZarckqaQgtqjZbuV2C2mrOHUs5uojlXaopj5gA4yJSGDcYhj1 + Rg9jdHWBtkHBj3eL32ZqrHDs3ap8ErZMmPE8A+mn9TTnQS+FY2QF7vBjJKM3qPT7 + DMVGWrg4m1NF8N6yMPMP + =RB1y + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + def primary_keyid fingerprint[-16..-1] end @@ -201,4 +241,277 @@ module GpgHelpers ['bette.cartwright@example.com', 'bette.cartwright@example.net'] end end + + # GPG Key with extra signing key + module User3 + extend self + + def signed_commit_signature + <<~SIGNATURE + -----BEGIN PGP SIGNATURE----- + + iQEzBAABCAAdFiEEBSLdKbmPFnzYQhdS44/8r3Wr2SoFAlnNlT8ACgkQ44/8r3Wr + 2SqP1wf9FC4J2S8LIHs/fpxgkYzsyCp5lCbS7JuoD4pqmI2KWyBx+vi9/3mZPCsm + Fj9f0vFEtNOb39GNGZbaA8DdGw30/WAS6kI6yco0WSK53KHrLw9Kqd+3e/NAVSsl + 991Gq4n8X1U5izSH+gZOMtEEUBGqIlZKgRrEh7lhNcz0G7JTF2VCE4NNtZdq7GDA + N6jOQxDGUwi9wQBYORQzIBc3NihfhGloII1hXf0XzrgUY3zNYHTT7QipCxKAmH54 + skwW+wi8RpBedar4saf7fs5xZbP/0yyVz98MJMdHBL68++Xt1AIHoqrb7eWszqnd + PCo/fnz1iHKCig602KLj0/zhADcNkg== + =LsTi + -----END PGP SIGNATURE----- + SIGNATURE + end + + def signed_commit_base_data + <<~SIGNEDDATA + tree 86ec18bfe87ad42a782fdabd8310f9b7ac750f51 + parent 2d1096e3a0ecf1d2baf6dee036cc80775d4940ba + author John Doe <john.doe@example.com> 1506645311 -0500 + committer John Doe <john.doe@example.com> 1506645311 -0500 + + Commit signed with subkey by John Doe + SIGNEDDATA + end + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFnNgbIBCAC9/WblcR4s/pFTwh9cm2iS59YRhtGfbrnfNE6QMIFIRFaK0u6J + LDy+scipXLoGg7X0XNFLW6Y457UUpkaPDVLPuVMONhMXdVqndGVvk9jq3D4oDtRa + ukucuXr9F84lHnjL3EosmAUiK3xBmHOGHm8+bvKbgPOdvre48YxoJ1POTen+chfo + YtLUfnC9EEGY/bn00aaXm3NV+zZK2zio5bFX9YLWSNh/JaXxuJsLoHR/lVrU7CLt + FCaGcPQ9SU46LHPshEYWO7ZsjEYJsYYOIOEzfcfR47T2EDTa6mVc++gC5TCoY3Ql + jccgm+EM0LvyEHwupEpxzCg2VsT0yoedhUhtABEBAAG0H0pvaG4gRG9lIDxqb2hu + LmRvZUBleGFtcGxlLmNvbT6JAVQEEwEIAD4WIQTqP4uIlyqP1HSHLy8RWzrxqtPt + ugUCWc2BsgIbAwUJA8JnAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRARWzrx + qtPturMDCACc1Pi1sLJFcCnJEc9sCInCO4LH8fntNjRLN3MTPU5YCYmFM3fAl5ly + vXPZ4jNWZxKbQVeFnkDOg5Ti8bzmFEMc8KbZuguktVFizxnLdFCTTRO89i3HDVSN + bIosrs5HJwRKOzul6i2whn3dsr8/P8WJczYjZGiw29hGwH3md4Thn/aAGbzjoCEF + IfIb1kccyHMJkaj79S8B2agsbEJLuTSfsXC3kGZIJyKG1DNmDUHW/ZE6ro/Kkhik + 3w6Jw14cMsKUIOBkOgsD/gXgX9xxWjYHmKrbCXucTKUevNlaCy5tzwrC0Am3prl9 + OJj3macOA8hNaTVDflEHNCwHOwqnVQYyuQENBFnNgbIBCAC59MmKc0cqPRPTpCZ5 + arKRoj23SNKWMDWaxSELdU91Wd/NJk4wF25urk9BtBuwjzaBMqb/xPD88yJC3ucs + 2LwCyAb5C/dHcPOpys8Pd+KrdHDR3zAMjcASsizlW/qFI9MtjhcU9Yd6iTUejAZG + NEC76WALJ3SLYzCaDkHFjWpH3Xq6ck3/9jpL3csn/5GLCsZQUDYGrZSXvHAIigwW + Xo6tMs5LCCO9CZg2qGDpvqlzcmy6CRkf0h/UFYJzGqfbJtxeCIxa93WIPE8eGwao + aneDlNtIoYiP6krC3OLsaPWT58QltNKaQuZSpjwtQBHa4JIt55vx+FcvRb7Kflgf + fT8bABEBAAGJATwEGAEIACYWIQTqP4uIlyqP1HSHLy8RWzrxqtPtugUCWc2BsgIb + DAUJA8JnAAAKCRARWzrxqtPtuqJjCACj+Z4qtgMpJXx3u58wCzkVLl5IylD/tEPA + cNIrj8QS8ec+woTJaMGVCh96VC2FPl8KR4Hjhy0yaupyPbTI6VWib63S/NcDfG7r + tviRFG2Gf8yduERebyC0cpgnmjVgFfJs7N3K3ncz6myOr9idNI05OC9poL73sDUv + jRXhm7uy938bT/R4MQdpYuxucgU3MiwvfG5ht+oJ4Yp+/IrR2PTqRGqMCsodnroa + TBKq2kW565TtCvrFkNub/ytorDbIVN9VrIMcuTiOv8sLoQRDJO9HvWUhYAqMY6Uh + dy12jR9FrquZnGsDKKs9V0Y6J4Wi8vnmdgWVZUc40pfXq3APAB6suQENBFnNgeAB + CADFqQRxLHxLIQ7B72diTMI2tPk9d5c67k+Gzkrg1QYoxBLdRCmhM4ydYqZzvIz4 + 1npkK20w4somOCwvyAOjO46IGb3f/Wk8mY8o5HMpI1miAfze0YTZKzRo2DmrvwbV + /h8jvZNCISwtrOgaaszWSVSuEQQCA1jf4qixfCb3ReETvZc3MTZXhw8IUbszXh5d + a6CYqq/fr5Zw4Dc19ZSoHSTh0Wn03mEm/kaYtia/wt1I+xVvTSaC2Pf/kUtr7UEf + 3NMc0YF0s4KgeW8KwjHa7Sz9wzydLPRH5kJ26SDUGUhrFf1jNLIegtDsoISV/86G + ztXcVq5GY6lXMwmsggRe++7xABEBAAGJAmwEGAEIACAWIQTqP4uIlyqP1HSHLy8R + WzrxqtPtugUCWc2B4AIbAgFACRARWzrxqtPtusB0IAQZAQgAHRYhBAUi3Sm5jxZ8 + 2EIXUuOP/K91q9kqBQJZzYHgAAoJEOOP/K91q9kqlHcH+wbvD14ITYDMfgIfy67O + 4Qcmgf1qzGXhpsABz/i/EPgRD990eNBI0YGuvoKRJfetEGn7LerrrCB8Z+ICFPHF + rzXoe10zm+QTREck0OB8nPFRycJ+Fbl6JX+cnkEx27Mmg1aVb7+H5LMDaWO1KjLs + g2wIDo/jrDfW7NoZzy4XTd7jFCOt4fftL/dhiujQmhLzugZXCxRISOVdmgilDJQo + Tz1sEm34ida98JFjdzSgkUvJ/pFTZ21ThCNxlUf01Hr2Pdcg1e2/97sZocMFTY2J + KwmiW2LG3B05/VrRtdvsCVj8G49coxgPPKty+m71ovAXdS/CvVqE7TefCplsYJ1d + V3abwwf/Xl2SxzbAKbrYMgZfdGzpPg2u6982WvfGIVfjFJh9veHZAbfkPcjdAD2X + e67Y4BeKG2OQQqeOY2y+81A7PaehgHzbFHJG/4RjqB66efrZAg4DgIdbr4oyMoUJ + VVsl0wfYSIvnd4kvWXYICVwk53HLA3wIowZAsJ1LT2byAKbUzayLzTekrTzGcwQk + g2XT798ev2QuR5Ki5x8MULBFX4Lhd03+uGOxjhNPQD6DAAHCQLaXQhaGuyMgt5hD + t0nF3yuw3Eg4Ygcbtm24rZXOHJc9bDKeRiWZz1dIyYYVJmHSQwOVXkAwJlF1sIgy + /jQYeOgFDKq20x86WulkvsUtBoaZJg== + =Q5Z7 + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def secret_key + <<~SECRET + -----BEGIN PGP PRIVATE KEY BLOCK----- + + lQPGBFnNgbIBCAC9/WblcR4s/pFTwh9cm2iS59YRhtGfbrnfNE6QMIFIRFaK0u6J + LDy+scipXLoGg7X0XNFLW6Y457UUpkaPDVLPuVMONhMXdVqndGVvk9jq3D4oDtRa + ukucuXr9F84lHnjL3EosmAUiK3xBmHOGHm8+bvKbgPOdvre48YxoJ1POTen+chfo + YtLUfnC9EEGY/bn00aaXm3NV+zZK2zio5bFX9YLWSNh/JaXxuJsLoHR/lVrU7CLt + FCaGcPQ9SU46LHPshEYWO7ZsjEYJsYYOIOEzfcfR47T2EDTa6mVc++gC5TCoY3Ql + jccgm+EM0LvyEHwupEpxzCg2VsT0yoedhUhtABEBAAH+BwMCOqjIWtWBMo3mjIP1 + OnIbZ+YJxSUZ/B8wU2loMv4XiKmeXLbjD6h3jojxYlnreSHA9QvoY8uNaWElL/n2 + jv6bxluivk8tA9FWJVv4HaSlMDd2J2YmUW17r8z9Kvm7b7pFVSrEoYV93Wdj5FJ7 + ciKrFhYNSD7tH1sHwkrFAbiv6aHyk9h48YmR3kx2wBvz+pWk7M2srCJx2b6DXkj/ + fsj1c/vnzUUGooOJgOvYAWrpg/rJUNxSsFypAHf8Xtk+xt8S1aZ9jaCmYk6I1B2L + m00HP43cXUpKcmETW1zXvfMLKjjoUEAJhSJhbCwiEzGL4ojQTarl8qbb+MisakEJ + DkPYtrhiiuVzUIFfqE86yO0UKidtzBmJAW3c6zeiUATvACzU09aGyUY1cJi93oXD + w4PCyVZ+nMvGD1wx+gyYdDINwpX4y6od9RDr06DGCzwu+S2vxsu1T8LdSv52fhBr + U0FY3Z3VN1ytay4SHi/8Y9VBYQFBh7R7Ch0gEMxLVKXVNqOXHUdGrKWV/WmyLKuZ + W9DEnWU4Mpz/di5jU8EDW7EB9DZZhVk3mQw3nuAZrBGD4azmmD5mgSgLeBGmKZ1e + q/9IWO44mRBBUtNv+rAkmmYF3MCNHuc7EMj+c/IgBUC7d5qBzGWA3UJ0vKX4xcIQ + X/PnU+nGxNvBrdqQaMLczeg28SerojxuX79prOsoySctLAbajd9HshW5SfOZ0rvb + BNHPqolQDijYEHGxANh4BbamRMGi60Rop7vJsZOLAemz17x/mvCtAHISOJT77/IM + oWC+IksJ5XsA/klJGe/tkx11aRQDDmKvIJXmMuRdvnIR23UBbzRQlWWq0l6CdoF6 + 6SQ9BJBFq0WY32No9WZAPnDO3buUzWc1Y3uwn/+h7TVYVyTlEqzpYJ9FoJwBHbor + 0663eoyz6+AUtB9Kb2huIERvZSA8am9obi5kb2VAZXhhbXBsZS5jb20+iQFUBBMB + CAA+FiEE6j+LiJcqj9R0hy8vEVs68arT7boFAlnNgbICGwMFCQPCZwAFCwkIBwIG + FQgJCgsCBBYCAwECHgECF4AACgkQEVs68arT7bqzAwgAnNT4tbCyRXApyRHPbAiJ + wjuCx/H57TY0SzdzEz1OWAmJhTN3wJeZcr1z2eIzVmcSm0FXhZ5AzoOU4vG85hRD + HPCm2boLpLVRYs8Zy3RQk00TvPYtxw1UjWyKLK7ORycESjs7peotsIZ93bK/Pz/F + iXM2I2RosNvYRsB95neE4Z/2gBm846AhBSHyG9ZHHMhzCZGo+/UvAdmoLGxCS7k0 + n7Fwt5BmSCcihtQzZg1B1v2ROq6PypIYpN8OicNeHDLClCDgZDoLA/4F4F/ccVo2 + B5iq2wl7nEylHrzZWgsubc8KwtAJt6a5fTiY95mnDgPITWk1Q35RBzQsBzsKp1UG + Mp0DxgRZzYGyAQgAufTJinNHKj0T06QmeWqykaI9t0jSljA1msUhC3VPdVnfzSZO + MBdubq5PQbQbsI82gTKm/8Tw/PMiQt7nLNi8AsgG+Qv3R3DzqcrPD3fiq3Rw0d8w + DI3AErIs5Vv6hSPTLY4XFPWHeok1HowGRjRAu+lgCyd0i2Mwmg5BxY1qR916unJN + //Y6S93LJ/+RiwrGUFA2Bq2Ul7xwCIoMFl6OrTLOSwgjvQmYNqhg6b6pc3JsugkZ + H9If1BWCcxqn2ybcXgiMWvd1iDxPHhsGqGp3g5TbSKGIj+pKwtzi7Gj1k+fEJbTS + mkLmUqY8LUAR2uCSLeeb8fhXL0W+yn5YH30/GwARAQAB/gcDAuYn/gmAA3OC5p5Q + Pat5kE7MtmSvSPmdjVA2o+6RtqZf81JqtAgtDVDwj7SPFsH6ue5P+iAn9938YYek + WQU2+0GXeUbSJt+u4VAchgwA5mYsEnEr1/E5KEfWPWO3jJol1rJG99adrjkMxvug + QJmwieqhu0368w1FU0tKstxYbr3Tz3nPCPDJoigMEUkXiFklDCUgeNk0g+zd5ytE + lXuuLYcGZX7njxL5jD+cMIKqua5zv8WbvNf/BhM1nCarxp4qzKWim8J8jY+iR+/E + qOar4aliGRez0j+qh/r8ywgPwfOO89zrKrMfaclL7dN9yuecmBHKWZvfrP5JKMHj + yTU3nRMhUGbfVUaaZI2Ccz2rNOU4oF9wuzpzQi8qOysZixRmH61Nw3ULIKoQgiWp + 0p5A3L94OaEfZEq3plVaIXI2YWYFSEAlIAc2dq4GxynousLdhNACi9bHhXrfFUhK + ckw1QlbhguO/j63/x8ygsmLZVwHG0fJZtMhT3+EGam9cuMBibIYyu3ufJRy7kMKt + kmyuk02X+hYJ7w8Pu6b8zYHBXbsEKamipMgd4oKtc8WnXILZo4lwDSArgs7ZVCBa + vGBbpTOsr54WjsyuCdX/wv0F2l31J87UxVtTKXx/+nfMfCE02zd+NsTgqvgqmkaA + Sy3qvv326kJNx7p+5hRwDzlAZ7vGJjj5TwCbGYDvctIf6MFrGDRNYwrGwNkPc3TG + rturfeL/ioua0Smj8LIbOv9Ir93gUIseNpxv8tXV/lffdIplcw802b3aXIKyv4fq + b9y3Oq/pDHFukKuBe9WTXJvjT0+ME+a0C8KIb/sts95pmjZsgN1kPmvuT0ReQwUR + eGrqz387bnVUzo4RgM3IERs/0EYzPzE8A2vc1e4/87b5J+1Xnov8Phd29vW8Td5l + ApiFrFO2r+/Np4kBPAQYAQgAJhYhBOo/i4iXKo/UdIcvLxFbOvGq0+26BQJZzYGy + AhsMBQkDwmcAAAoJEBFbOvGq0+26omMIAKP5niq2AyklfHe7nzALORUuXkjKUP+0 + Q8Bw0iuPxBLx5z7ChMlowZUKH3pULYU+XwpHgeOHLTJq6nI9tMjpVaJvrdL81wN8 + buu2+JEUbYZ/zJ24RF5vILRymCeaNWAV8mzs3credzPqbI6v2J00jTk4L2mgvvew + NS+NFeGbu7L3fxtP9HgxB2li7G5yBTcyLC98bmG36gnhin78itHY9OpEaowKyh2e + uhpMEqraRbnrlO0K+sWQ25v/K2isNshU31Wsgxy5OI6/ywuhBEMk70e9ZSFgCoxj + pSF3LXaNH0Wuq5mcawMoqz1XRjonhaLy+eZ2BZVlRzjSl9ercA8AHqydA8YEWc2B + 4AEIAMWpBHEsfEshDsHvZ2JMwja0+T13lzruT4bOSuDVBijEEt1EKaEzjJ1ipnO8 + jPjWemQrbTDiyiY4LC/IA6M7jogZvd/9aTyZjyjkcykjWaIB/N7RhNkrNGjYOau/ + BtX+HyO9k0IhLC2s6BpqzNZJVK4RBAIDWN/iqLF8JvdF4RO9lzcxNleHDwhRuzNe + Hl1roJiqr9+vlnDgNzX1lKgdJOHRafTeYSb+Rpi2Jr/C3Uj7FW9NJoLY9/+RS2vt + QR/c0xzRgXSzgqB5bwrCMdrtLP3DPJ0s9EfmQnbpINQZSGsV/WM0sh6C0OyghJX/ + zobO1dxWrkZjqVczCayCBF777vEAEQEAAf4HAwKESvCIDq5QNeadnSvpkZemItPO + lDf+7Wiue2gt776D5xkVyT7WkgTQv+IGWGtqz7pCCO2rMp/F9u1BghdjY46jtrK6 + MMFKta4YENUhRliH6M2YmRjq5p7xZgH6UOnDlqsafbfyUx30t59tbQj+07aMnH5J + LMm37nVkDvo3wpPQPuo7L6qizYsrHrQKeJZ8636u41UjC99lVH7vXzqXw68FJImi + XdMZbEVBIprYfCDem+fD6gJBA4JBqWJMxuFMfhWp+1WtYoeNojDm4KxBzc2fvYV/ + HOIUfLFBvACD/UwU5ovllHN39/O8SMgyLm9ymx2/qXcdIkUz4l7fhOCY1OW12DMu + 5OFrrTteGK/Sj4Z8pYRdMdaKyjIlxuVzEQGWsU5+J2ALao5atEHguqwlD3cKh3G8 + 1sA/l5eTFDt84erYv1MVStV0BhZaCE4mNL4WpnQGDdW05yoGq9jIyLcurb/k/atU + TUkAF1csgNlJlR3IP+7U9xfHkjMO5+SV82xoNf9nBjz06TRdnvOSKsMNKp0RxC/L + Hbiee9o7Rxqdiyv0ly6bCCymwfvlsEIqo3YKssBfe3XI5yQI2hF9QZaH1ywzmgaH + o+rbME/XxddRJueS79eipT7K05Z3ulSHTXzpDw+jZcdUV0Ac72Q9FTDPMl3xc6NW + DrYwWw/3+kyZ4SkP56l7KlGczTyNPvU9iou4Cj/cAZk/pHx68Chq8ZZNznFm/bIF + gWt3fqE/n+y78B6MI8qTjGJOR0jycxrLH82Z2F+FpMShI2C5NnOa/Ilkv3e2Q5U6 + MOAwaCIz6RHhcI99O/yta2vLelWZqn2g86rLzTG0HlIABTCPYotwetHh0hsrkSv9 + Kh6rOzGB4i8lRqcBVY+alMSiRBlzkwpL4YUcO6f3vEDncQ9evE1AQCpD4jUJjB1H + JSSJAmwEGAEIACAWIQTqP4uIlyqP1HSHLy8RWzrxqtPtugUCWc2B4AIbAgFACRAR + WzrxqtPtusB0IAQZAQgAHRYhBAUi3Sm5jxZ82EIXUuOP/K91q9kqBQJZzYHgAAoJ + EOOP/K91q9kqlHcH+wbvD14ITYDMfgIfy67O4Qcmgf1qzGXhpsABz/i/EPgRD990 + eNBI0YGuvoKRJfetEGn7LerrrCB8Z+ICFPHFrzXoe10zm+QTREck0OB8nPFRycJ+ + Fbl6JX+cnkEx27Mmg1aVb7+H5LMDaWO1KjLsg2wIDo/jrDfW7NoZzy4XTd7jFCOt + 4fftL/dhiujQmhLzugZXCxRISOVdmgilDJQoTz1sEm34ida98JFjdzSgkUvJ/pFT + Z21ThCNxlUf01Hr2Pdcg1e2/97sZocMFTY2JKwmiW2LG3B05/VrRtdvsCVj8G49c + oxgPPKty+m71ovAXdS/CvVqE7TefCplsYJ1dV3abwwf/Xl2SxzbAKbrYMgZfdGzp + Pg2u6982WvfGIVfjFJh9veHZAbfkPcjdAD2Xe67Y4BeKG2OQQqeOY2y+81A7Paeh + gHzbFHJG/4RjqB66efrZAg4DgIdbr4oyMoUJVVsl0wfYSIvnd4kvWXYICVwk53HL + A3wIowZAsJ1LT2byAKbUzayLzTekrTzGcwQkg2XT798ev2QuR5Ki5x8MULBFX4Lh + d03+uGOxjhNPQD6DAAHCQLaXQhaGuyMgt5hDt0nF3yuw3Eg4Ygcbtm24rZXOHJc9 + bDKeRiWZz1dIyYYVJmHSQwOVXkAwJlF1sIgy/jQYeOgFDKq20x86WulkvsUtBoaZ + Jg== + =TKlF + -----END PGP PRIVATE KEY BLOCK----- + SECRET + end + + def fingerprint + 'EA3F8B88972A8FD474872F2F115B3AF1AAD3EDBA' + end + + def subkey_fingerprints + %w(159AD5DDF199591D67D2B87AA3CEC5C0A7C270EC 0522DD29B98F167CD8421752E38FFCAF75ABD92A) + end + + def names + ['John Doe'] + end + + def emails + ['john.doe@example.com'] + end + end + + # GPG Key containing just the main key + module User4 + extend self + + def public_key + <<~KEY.strip + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK + aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew + PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ + TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j + SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K + GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAG0G0pvaG4gRG9lIDxqb2hu + QGV4YW1wbGUuY29tPokBTgQTAQgAOBYhBAh0izYM0lwuzJnVlAcBbPnhOj+bBQJZ + 1nHrAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEAcBbPnhOj+bkywH/i4w + OwpDxoTjUQlPlqGAGuzvWaPzSJndawgmMTr68oRsD+wlQmQQTR5eqxCpUIyV4aYb + D697RYzoqbT4mlU49ymzfKSAxFe88r1XQWdm81DcofHVPmw2GBrIqaX3Du4Z7xkI + Q9/S43orwknh5FoVwU8Nau7qBuv9vbw2apSkuA1oBj3spQ8hqwLavACyQ+fQloAT + hSDNqPiCZj6L0dwM1HYiqVoN3Q7qjgzzeBzlXzljJoWblhxllvMK20bVoa7H+uR2 + lczFHfsX8VTIMjyTGP7R3oHN91DEahlQybVVNLmNSDKZM2P/0d28BRUmWxQJ4Ws3 + J4hOWDKnLMed3VOIWzM= + =xVuW + -----END PGP PUBLIC KEY BLOCK----- + KEY + end + + def secret_key + <<~KEY.strip + -----BEGIN PGP PRIVATE KEY BLOCK----- + + lQPGBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK + aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew + PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+ + TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j + SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K + GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAH+BwMC4UwgHgH5Cp7meY39 + G5Q3GV2xtwADoaAvlOvPOLPK2fQqxQfb4WN4eZECp2wQuMRBMj52c4i9yphab1mQ + vOzoPIRGvkcJoxG++OxQ0kRk0C0gX6wM6SGVdb1nQnfZnoJCCU3IwCaSGktkLDs1 + jwdI+VmXJbSugUbd25bakHQcE2BaNHuRBlQWQfFbhGBy0+uMfNDBZ6FRipBu47hO + f/wm/xXuV8N8BSgvNR/qtAqSQI34CdsnWAhMYm9rqmTNyt0nq4dveX+E0YzVn4lH + lOEa7cpYeuBwIL8L3EvSPNCICiJlF3gVqiYzyqRElnCkv1OGc0x3W5onY/agHgGZ + KYyi/ubOdqqDgBR+eMt0JKSGH2EPxUAGFPY5F37u4erdxH86GzIinAExLSmADiVR + KtxluZP6S2KLbETN5uVbrfa+HVcMbbUZaBHHtL+YbY8PqaFUIvIUR1HM2SK7IrFw + KuQ8ibRgooyP7VgMNiPzlFpY4NXUv+FXIrNJ6ELuIaENi0izJ7aIbVBM8SijDz6u + 5EEmodnDvmU2hmQNZJ17TxggE7oeT0rKdDGHM5zBvqZ3deqE9sgKx/aTKcj61ID3 + M80ZkHPDFazUCohLpYgFN20bYYSmxU4LeNFy8YEiuic8QQKaAFxSf9Lf87UFQwyF + dduI1RWEbjMsbEJXwlmGM02ssQHsgoVKwZxijq5A5R1Ul6LowazQ8obPiwRS4NZ4 + Z+QKDon79MMXiFEeh1jeG/MKKWPxFg3pdtCWhC7WdH4hfkBsCVKf+T58yB2Gzziy + fOHvAl7v3PtdZgf1xikF8spGYGCWo4B2lxC79xIflKAb2U6myb5I4dpUYxzxoMxT + zxHwxEie3NxzZGUyXSt3LqYe2r4CxWnOCXWjIxxRlLue1BE5Za1ycnDRjgUO24+Z + uDQne6KLkhAotBtKb2huIERvZSA8am9obkBleGFtcGxlLmNvbT6JAU4EEwEIADgW + IQQIdIs2DNJcLsyZ1ZQHAWz54To/mwUCWdZx6wIbAwULCQgHAgYVCAkKCwIEFgID + AQIeAQIXgAAKCRAHAWz54To/m5MsB/4uMDsKQ8aE41EJT5ahgBrs71mj80iZ3WsI + JjE6+vKEbA/sJUJkEE0eXqsQqVCMleGmGw+ve0WM6Km0+JpVOPcps3ykgMRXvPK9 + V0FnZvNQ3KHx1T5sNhgayKml9w7uGe8ZCEPf0uN6K8JJ4eRaFcFPDWru6gbr/b28 + NmqUpLgNaAY97KUPIasC2rwAskPn0JaAE4Ugzaj4gmY+i9HcDNR2IqlaDd0O6o4M + 83gc5V85YyaFm5YcZZbzCttG1aGux/rkdpXMxR37F/FUyDI8kxj+0d6BzfdQxGoZ + UMm1VTS5jUgymTNj/9HdvAUVJlsUCeFrNyeITlgypyzHnd1TiFsz + =/37z + -----END PGP PRIVATE KEY BLOCK----- + KEY + end + + def primary_keyid + fingerprint[-16..-1] + end + + def fingerprint + '08748B360CD25C2ECC99D59407016CF9E13A3F9B' + end + end end diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb new file mode 100644 index 00000000000..c98aa503ed1 --- /dev/null +++ b/spec/support/helpers/merge_request_diff_helpers.rb @@ -0,0 +1,28 @@ +module MergeRequestDiffHelpers + def click_diff_line(line_holder, diff_side = nil) + line = get_line_components(line_holder, diff_side) + line[:content].hover + line[:num].find('.add-diff-note', visible: false).send_keys(:return) + end + + def get_line_components(line_holder, diff_side = nil) + if diff_side.nil? + get_inline_line_components(line_holder) + else + get_parallel_line_components(line_holder, diff_side) + end + end + + def get_inline_line_components(line_holder) + { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } + end + + def get_parallel_line_components(line_holder, diff_side = nil) + side_index = diff_side == 'left' ? 0 : 1 + # Wait for `.line_content` + line_holder.find('.line_content', match: :first) + # Wait for `.diff-line-num` + line_holder.find('.diff-line-num', match: :first) + { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } + end +end diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb index 86008698692..79a0aa174b1 100644 --- a/spec/support/helpers/note_interaction_helpers.rb +++ b/spec/support/helpers/note_interaction_helpers.rb @@ -2,7 +2,7 @@ module NoteInteractionHelpers def open_more_actions_dropdown(note) note_element = find("#note_#{note.id}") - note_element.find('.more-actions-toggle').trigger('click') + note_element.find('.more-actions-toggle').click note_element.find('.more-actions .dropdown-menu li', match: :first) end end diff --git a/spec/support/input_helper.rb b/spec/support/input_helper.rb new file mode 100644 index 00000000000..acbb42274ec --- /dev/null +++ b/spec/support/input_helper.rb @@ -0,0 +1,7 @@ +# see app/assets/javascripts/test_utils/simulate_input.js + +module InputHelper + def simulate_input(selector, input = '') + evaluate_script("window.simulateInput(#{selector.to_json}, #{input.to_json});") + end +end diff --git a/spec/support/inspect_requests.rb b/spec/support/inspect_requests.rb new file mode 100644 index 00000000000..88ddc5c7f6c --- /dev/null +++ b/spec/support/inspect_requests.rb @@ -0,0 +1,17 @@ +require_relative './wait_for_requests' + +module InspectRequests + extend self + include WaitForRequests + + def inspect_requests(inject_headers: {}) + Gitlab::Testing::RequestInspectorMiddleware.log_requests!(inject_headers) + + yield + + wait_for_all_requests + Gitlab::Testing::RequestInspectorMiddleware.requests + ensure + Gitlab::Testing::RequestInspectorMiddleware.stop_logging! + end +end diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb index 0b5f66597fd..88a7aeba461 100644 --- a/spec/support/jira_service_helper.rb +++ b/spec/support/jira_service_helper.rb @@ -6,6 +6,8 @@ module JiraServiceHelper properties = { title: "JIRA tracker", url: JIRA_URL, + username: 'jira-user', + password: 'my-secret-password', project_key: "JIRA", jira_issue_transition_id: '1' } diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb index 079f244475c..28d39a32f02 100644 --- a/spec/support/ldap_helpers.rb +++ b/spec/support/ldap_helpers.rb @@ -15,10 +15,7 @@ module LdapHelpers # admin_group: 'my-admin-group' # ) def stub_ldap_config(messages) - messages.each do |config, value| - allow_any_instance_of(::Gitlab::LDAP::Config) - .to receive(config.to_sym).and_return(value) - end + allow_any_instance_of(::Gitlab::LDAP::Config).to receive_messages(messages) end # Stub an LDAP person search and provide the return entry. Specify `nil` for diff --git a/spec/support/ldap_shared_examples.rb b/spec/support/ldap_shared_examples.rb new file mode 100644 index 00000000000..52c34e78965 --- /dev/null +++ b/spec/support/ldap_shared_examples.rb @@ -0,0 +1,69 @@ +shared_examples_for 'normalizes a DN' do + using RSpec::Parameterized::TableSyntax + + where(:test_description, :given, :expected) do + 'strips extraneous whitespace' | 'uid =John Smith , ou = People, dc= example,dc =com' | 'uid=john smith,ou=people,dc=example,dc=com' + 'strips extraneous whitespace for a DN with a single RDN' | 'uid = John Smith' | 'uid=john smith' + 'unescapes non-reserved, non-special Unicode characters' | 'uid = Sebasti\\c3\\a1n\\ C.\\20Smith, ou=People (aka. \\22humans\\") ,dc=example, dc=com' | 'uid=sebastián c. smith,ou=people (aka. \\"humans\\"),dc=example,dc=com' + 'downcases the whole string' | 'UID=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' + 'for a null DN (empty string), returns empty string and does not error' | '' | '' + 'does not strip an escaped leading space in an attribute value' | 'uid=\\ John Smith,ou=People,dc=example,dc=com' | 'uid=\\ john smith,ou=people,dc=example,dc=com' + 'does not strip an escaped leading space in the last attribute value' | 'uid=\\ John Smith' | 'uid=\\ john smith' + 'does not strip an escaped trailing space in an attribute value' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com' + 'strips extraneous spaces after an escaped trailing space' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com' + 'strips extraneous spaces after an escaped trailing space at the end of the DN' | 'uid=John Smith,ou=People,dc=example,dc=com\\ ' | 'uid=john smith,ou=people,dc=example,dc=com\\ ' + 'properly preserves escaped trailing space after unescaped trailing spaces' | 'uid=John Smith \\ ,ou=People,dc=example,dc=com' | 'uid=john smith \\ ,ou=people,dc=example,dc=com' + 'preserves multiple inner spaces in an attribute value' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' + 'preserves inner spaces after an escaped space' | 'uid=John\\ Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' + 'hex-escapes an escaped leading newline in an attribute value' | "uid=\\\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com" + 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "uid=John Smith\\\n,ou=People,dc=example,dc=com" | "uid=john smith\\0a,ou=people,dc=example,dc=com" + 'hex-escapes an unescaped leading newline (actually an invalid DN?)' | "uid=\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com" + 'strips an unescaped trailing newline (actually an invalid DN?)' | "uid=John Smith\n,ou=People,dc=example,dc=com" | "uid=john smith,ou=people,dc=example,dc=com" + 'does not strip if no extraneous whitespace' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com' + 'does not modify an escaped equal sign in an attribute value' | 'uid= foo \\= bar' | 'uid=foo \\= bar' + 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | 'uid= foo \\3D bar' | 'uid=foo \\= bar' + 'does not modify an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\, CA' | 'uid=john c. smith,ou=san francisco\\, ca' + 'converts an escaped hex comma to an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\2C CA' | 'uid=john c. smith,ou=san francisco\\, ca' + 'does not modify an escaped hex carriage return character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0DCA' | 'uid=john c. smith,ou=san francisco\\,\\0dca' + 'does not modify an escaped hex line feed character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0aca' + 'does not modify an escaped hex CRLF in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0D\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0d\\0aca' + 'allows attribute type name OIDs' | '0.9.2342.19200300.100.1.25=Example,0.9.2342.19200300.100.1.25=Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com' + 'strips extraneous whitespace from attribute type name OIDs' | '0.9.2342.19200300.100.1.25 = Example, 0.9.2342.19200300.100.1.25 = Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com' + end + + with_them do + it 'normalizes the DN' do + assert_generic_test(test_description, subject, expected) + end + end +end + +shared_examples_for 'normalizes a DN attribute value' do + using RSpec::Parameterized::TableSyntax + + where(:test_description, :given, :expected) do + 'strips extraneous whitespace' | ' John Smith ' | 'john smith' + 'unescapes non-reserved, non-special Unicode characters' | 'Sebasti\\c3\\a1n\\ C.\\20Smith' | 'sebastián c. smith' + 'downcases the whole string' | 'JoHn C. Smith' | 'john c. smith' + 'does not strip an escaped leading space in an attribute value' | '\\ John Smith' | '\\ john smith' + 'does not strip an escaped trailing space in an attribute value' | 'John Smith\\ ' | 'john smith\\ ' + 'hex-escapes an escaped leading newline in an attribute value' | "\\\nJohn Smith" | "\\0ajohn smith" + 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "John Smith\\\n" | "john smith\\0a" + 'hex-escapes an unescaped leading newline (actually an invalid DN value?)' | "\nJohn Smith" | "\\0ajohn smith" + 'strips an unescaped trailing newline (actually an invalid DN value?)' | "John Smith\n" | "john smith" + 'does not strip if no extraneous whitespace' | 'John Smith' | 'john smith' + 'does not modify an escaped equal sign in an attribute value' | ' foo \\= bar' | 'foo \\= bar' + 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | ' foo \\3D bar' | 'foo \\= bar' + 'does not modify an escaped comma in an attribute value' | 'San Francisco\\, CA' | 'san francisco\\, ca' + 'converts an escaped hex comma to an escaped comma in an attribute value' | 'San Francisco\\2C CA' | 'san francisco\\, ca' + 'does not modify an escaped hex carriage return character in an attribute value' | 'San Francisco\\,\\0DCA' | 'san francisco\\,\\0dca' + 'does not modify an escaped hex line feed character in an attribute value' | 'San Francisco\\,\\0ACA' | 'san francisco\\,\\0aca' + 'does not modify an escaped hex CRLF in an attribute value' | 'San Francisco\\,\\0D\\0ACA' | 'san francisco\\,\\0d\\0aca' + end + + with_them do + it 'normalizes the DN attribute value' do + assert_generic_test(test_description, subject, expected) + end + end +end diff --git a/spec/support/live_debugger.rb b/spec/support/live_debugger.rb new file mode 100644 index 00000000000..911eb48a8ca --- /dev/null +++ b/spec/support/live_debugger.rb @@ -0,0 +1,17 @@ +require 'io/console' + +module LiveDebugger + def live_debug + puts + puts "Current example is paused for live debugging." + puts "Opening #{current_url} in your default browser..." + puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user + puts "Press any key to resume the execution of the example!!" + + `open #{current_url}` + + loop until $stdin.getch + + puts "Back to the example!" + end +end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 3e117530151..50702a0ac88 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -3,6 +3,21 @@ require_relative 'devise_helpers' module LoginHelpers include DeviseHelpers + # Overriding Devise::Test::IntegrationHelpers#sign_in to store @current_user + # since we may need it in LiveDebugger#live_debug. + def sign_in(resource, scope: nil) + super + + @current_user = resource + end + + # Overriding Devise::Test::IntegrationHelpers#sign_out to clear @current_user. + def sign_out(resource_or_scope) + super + + @current_user = nil + end + # Internal: Log in as a specific user or a new user of a specific role # # user_or_role - User object, or a role to create (e.g., :admin, :user) @@ -28,7 +43,7 @@ module LoginHelpers gitlab_sign_in_with(user, **kwargs) - user + @current_user = user end def gitlab_sign_in_via(provider, user, uid) @@ -41,6 +56,7 @@ module LoginHelpers def gitlab_sign_out find(".header-user-dropdown-toggle").click click_link "Sign out" + @current_user = nil expect(page).to have_button('Sign in') end @@ -120,4 +136,16 @@ module LoginHelpers allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') end + + def stub_omniauth_config(messages) + allow(Gitlab.config.omniauth).to receive_messages(messages) + end + + def stub_basic_saml_config + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) + end + + def stub_saml_group_config(groups) + allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + end end diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb index 431f20a2a5c..3b9eb84e824 100644 --- a/spec/support/mobile_helpers.rb +++ b/spec/support/mobile_helpers.rb @@ -12,6 +12,6 @@ module MobileHelpers end def resize_window(width, height) - page.driver.resize_window width, height + Capybara.current_session.current_window.resize_to(width, height) end end diff --git a/spec/support/project_forks_helper.rb b/spec/support/project_forks_helper.rb new file mode 100644 index 00000000000..d6680735aa1 --- /dev/null +++ b/spec/support/project_forks_helper.rb @@ -0,0 +1,58 @@ +module ProjectForksHelper + def fork_project(project, user = nil, params = {}) + # Load the `fork_network` for the project to fork as there might be one that + # wasn't loaded yet. + project.reload unless project.fork_network + + unless user + user = create(:user) + project.add_developer(user) + end + + unless params[:namespace] || params[:namespace_id] + params[:namespace] = create(:group) + params[:namespace].add_owner(user) + end + + service = Projects::ForkService.new(project, user, params) + + create_repository = params.delete(:repository) + # Avoid creating a repository + unless create_repository + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + shell = double('gitlab_shell', fork_repository: true) + allow(service).to receive(:gitlab_shell).and_return(shell) + end + + forked_project = service.execute + + # Reload the both projects so they know about their newly created fork_network + if forked_project.persisted? + project.reload + forked_project.reload + end + + if create_repository + # The call to project.repository.after_import in RepositoryForkWorker does + # not reset the @exists variable of this forked_project.repository + # so we have to explicitely call this method to clear the @exists variable. + # of the instance we're returning here. + forked_project.repository.after_import + + # We can't leave the hooks in place after a fork, as those would fail in tests + # The "internal" API is not available + FileUtils.rm_rf("#{forked_project.repository.path}/hooks") + end + + forked_project + end + + def fork_project_with_submodules(project, user = nil, params = {}) + forked_project = fork_project(project, user, params) + TestEnv.copy_repo(forked_project, + bare_repo: TestEnv.forked_repo_path_bare, + refs: TestEnv::FORKED_BRANCH_SHA) + forked_project.repository.after_import + forked_project + end +end diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb index 421a51fc336..2770cdcbefc 100644 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb @@ -9,7 +9,7 @@ RSpec.shared_examples "protected tags > access control > CE" do allowed_to_create_button = find(".js-allowed-to-create") unless allowed_to_create_button.text == access_type_name - allowed_to_create_button.trigger('click') + allowed_to_create_button.click find('.create_access_levels-container .dropdown-menu li', match: :first) within('.create_access_levels-container .dropdown-menu') { click_on access_type_name } end diff --git a/spec/support/quick_actions_helpers.rb b/spec/support/quick_actions_helpers.rb index d2aaae7518f..361190aa352 100644 --- a/spec/support/quick_actions_helpers.rb +++ b/spec/support/quick_actions_helpers.rb @@ -3,7 +3,7 @@ module QuickActionsHelpers Sidekiq::Testing.fake! do page.within('.js-main-target-form') do fill_in 'note[note]', with: text - find('.js-comment-submit-button').trigger('click') + find('.js-comment-submit-button').click end end end diff --git a/spec/support/redis_without_keys.rb b/spec/support/redis_without_keys.rb new file mode 100644 index 00000000000..6220167dee6 --- /dev/null +++ b/spec/support/redis_without_keys.rb @@ -0,0 +1,8 @@ +class Redis + ForbiddenCommand = Class.new(StandardError) + + def keys(*args) + raise ForbiddenCommand.new("Don't use `Redis#keys` as it iterates over all "\ + "keys in redis. Use `Redis#scan_each` instead.") + end +end diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb index 6b1853c2364..55da961e173 100644 --- a/spec/support/select2_helper.rb +++ b/spec/support/select2_helper.rb @@ -16,6 +16,7 @@ module Select2Helper selector = options.fetch(:from) + first(selector, visible: false) if options[:multiple] execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');") else diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb new file mode 100644 index 00000000000..221926aaf7e --- /dev/null +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -0,0 +1,28 @@ +shared_examples 'comment on merge request file' do + it 'adds a comment' do + click_diff_line(find("[id='#{sample_commit.line_code}']")) + + page.within('.js-discussion-note-form') do + fill_in(:note_note, with: 'Line is wrong') + click_button('Comment') + end + + wait_for_requests + + page.within('.notes_holder') do + expect(page).to have_content('Line is wrong') + end + + visit(merge_request_path(merge_request)) + + page.within('.notes .discussion') do + expect(page).to have_content("#{user.name} #{user.to_reference} started a discussion") + expect(page).to have_content(sample_commit.line_code_path) + expect(page).to have_content('Line is wrong') + end + + page.within('.notes-tab .badge') do + expect(page).to have_content('1') + end + end +end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index d5bc12f3bc5..5fde91512da 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -9,7 +9,7 @@ shared_examples "protected branches > access control > CE" do allowed_to_push_button = find(".js-allowed-to-push") unless allowed_to_push_button.text == access_type_name - allowed_to_push_button.trigger('click') + allowed_to_push_button.click within(".dropdown.open .dropdown-menu") { click_on access_type_name } end end @@ -34,7 +34,7 @@ shared_examples "protected branches > access control > CE" do within('.js-allowed-to-push-container') do expect(first("li")).to have_content("Roles") - click_on access_type_name + find(:link, access_type_name).click end end @@ -79,7 +79,7 @@ shared_examples "protected branches > access control > CE" do within('.js-allowed-to-merge-container') do expect(first("li")).to have_content("Roles") - click_on access_type_name + find(:link, access_type_name).click end end diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb new file mode 100644 index 00000000000..a4762b68858 --- /dev/null +++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb @@ -0,0 +1,57 @@ +# This shared example requires a `builder` and `user` variable +shared_examples 'issuable hook data' do |kind| + let(:data) { builder.build(user: user) } + + include_examples 'project hook data' do + let(:project) { builder.issuable.project } + end + include_examples 'deprecated repository hook data' + + context "with a #{kind}" do + it 'contains issuable data' do + expect(data[:object_kind]).to eq(kind) + expect(data[:user]).to eq(user.hook_attrs) + expect(data[:project]).to eq(builder.issuable.project.hook_attrs) + expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:changes]).to eq({}) + expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) + end + + it 'does not contain certain keys' do + expect(data).not_to have_key(:assignees) + expect(data).not_to have_key(:assignee) + end + + describe 'changes are given' do + let(:changes) do + { + cached_markdown_version: %w[foo bar], + description: ['A description', 'A cool description'], + description_html: %w[foo bar], + in_progress_merge_commit_sha: %w[foo bar], + lock_version: %w[foo bar], + merge_jid: %w[foo bar], + title: ['A title', 'Hello World'], + title_html: %w[foo bar] + } + end + let(:data) { builder.build(user: user, changes: changes) } + + it 'populates the :changes hash' do + expect(data[:changes]).to match(hash_including({ + title: { previous: 'A title', current: 'Hello World' }, + description: { previous: 'A description', current: 'A cool description' } + })) + end + + it 'does not contain certain keys' do + expect(data[:changes]).not_to have_key('cached_markdown_version') + expect(data[:changes]).not_to have_key('description_html') + expect(data[:changes]).not_to have_key('lock_version') + expect(data[:changes]).not_to have_key('title_html') + expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') + expect(data[:changes]).not_to have_key('merge_jid') + end + end + end +end diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb index 1eb405d4be8..f0264878811 100644 --- a/spec/support/project_hook_data_shared_example.rb +++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb @@ -1,4 +1,4 @@ -RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project| +shared_examples 'project hook data with deprecateds' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:description]).to eq(project.description) @@ -17,7 +17,7 @@ RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :pro end end -RSpec.shared_examples 'project hook data' do |project_key: :project| +shared_examples 'project hook data' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:description]).to eq(project.description) @@ -32,7 +32,7 @@ RSpec.shared_examples 'project hook data' do |project_key: :project| end end -RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project| +shared_examples 'deprecated repository hook data' do it 'contains deprecated repository data' do expect(data[:repository][:name]).to eq(project.name) expect(data[:repository][:description]).to eq(project.description) diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb new file mode 100644 index 00000000000..ffc9456dbc7 --- /dev/null +++ b/spec/support/shared_examples/position_formatters.rb @@ -0,0 +1,43 @@ +shared_examples_for "position formatter" do + let(:formatter) { described_class.new(attrs) } + + describe '#key' do + let(:key) { [123, 456, 789, Digest::SHA1.hexdigest(formatter.old_path), Digest::SHA1.hexdigest(formatter.new_path), 1, 2] } + + subject { formatter.key } + + it { is_expected.to eq(key) } + end + + describe '#complete?' do + subject { formatter.complete? } + + context 'when there are missing key attributes' do + it { is_expected.to be_truthy } + end + + context 'when old_line and new_line are nil' do + let(:attrs) { base_attrs } + + it { is_expected.to be_falsy } + end + end + + describe '#to_h' do + let(:formatter_hash) do + attrs.merge(position_type: base_attrs[:position_type] || 'text' ) + end + + subject { formatter.to_h } + + it { is_expected.to eq(formatter_hash) } + end + + describe '#==' do + subject { formatter } + + let(:other_formatter) { described_class.new(attrs) } + + it { is_expected.to eq(other_formatter) } + end +end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb new file mode 100644 index 00000000000..6bc39f2f279 --- /dev/null +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -0,0 +1,103 @@ +shared_examples 'custom attributes endpoints' do |attributable_name| + let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' } + let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' } + + describe "GET /#{attributable_name} with custom attributes filter" do + let!(:other_attributable) { create attributable.class.name.underscore } + + context 'with an unauthorized user' do + it 'does not filter by custom attributes' do + get api("/#{attributable_name}", user), custom_attributes: { foo: 'foo', bar: 'bar' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 2 + end + end + + it 'filters by custom attributes' do + get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to be 1 + expect(json_response.first['id']).to eq attributable.id + end + end + + describe "GET /#{attributable_name}/:id/custom_attributes" do + context 'with an unauthorized user' do + subject { get api("/#{attributable_name}/#{attributable.id}/custom_attributes", user) } + + it_behaves_like 'an unauthorized API user' + end + + it 'returns all custom attributes' do + get api("/#{attributable_name}/#{attributable.id}/custom_attributes", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to contain_exactly( + { 'key' => 'foo', 'value' => 'foo' }, + { 'key' => 'bar', 'value' => 'bar' } + ) + end + end + + describe "GET /#{attributable_name}/:id/custom_attributes/:key" do + context 'with an unauthorized user' do + subject { get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user) } + + it_behaves_like 'an unauthorized API user' + end + + it 'returns a single custom attribute' do + get api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'foo', 'value' => 'foo' }) + end + end + + describe "PUT /#{attributable_name}/:id/custom_attributes/:key" do + context 'with an unauthorized user' do + subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), value: 'new' } + + it_behaves_like 'an unauthorized API user' + end + + it 'creates a new custom attribute' do + expect do + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new' + end.to change { attributable.custom_attributes.count }.by(1) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'new', 'value' => 'new' }) + expect(attributable.custom_attributes.find_by(key: 'new').value).to eq 'new' + end + + it 'updates an existing custom attribute' do + expect do + put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new' + end.not_to change { attributable.custom_attributes.count } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq({ 'key' => 'foo', 'value' => 'new' }) + expect(custom_attribute1.reload.value).to eq 'new' + end + end + + describe "DELETE /#{attributable_name}/:id/custom_attributes/:key" do + context 'with an unauthorized user' do + subject { delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user) } + + it_behaves_like 'an unauthorized API user' + end + + it 'deletes an existing custom attribute' do + expect do + delete api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin) + end.to change { attributable.custom_attributes.count }.by(-1) + + expect(response).to have_gitlab_http_status(204) + expect(attributable.custom_attributes.find_by(key: 'foo')).to be_nil + end + end +end diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index 7d7f66adeab..0ed917e448a 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -3,6 +3,8 @@ # Requires an API request: # let(:request) { get api("/projects/#{project.id}/repository/branches", user) } shared_examples_for '400 response' do + let(:message) { nil } + before do # Fires the request request @@ -10,6 +12,10 @@ shared_examples_for '400 response' do it 'returns 400' do expect(response).to have_gitlab_http_status(400) + + if message.present? + expect(json_response['message']).to eq(message) + end end end @@ -26,6 +32,7 @@ end shared_examples_for '404 response' do let(:message) { nil } + before do # Fires the request request diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 6accf16bea4..17f3a861ba8 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -76,8 +76,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do message: "user created page: Awesome wiki_page" } - wiki_page_service = WikiPages::CreateService.new(project, user, opts) - @wiki_page = wiki_page_service.execute + @wiki_page = create(:wiki_page, wiki: project.wiki, attrs: opts) @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create') end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index 2dfb4d4a07f..4ead78529c3 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -38,15 +38,15 @@ module StubConfiguration allow(Gitlab.config.backup).to receive_messages(to_settings(messages)) end + def stub_lfs_setting(messages) + allow(Gitlab.config.lfs).to receive_messages(to_settings(messages)) + end + def stub_storage_settings(messages) # Default storage is always required messages['default'] ||= Gitlab.config.repositories.storages.default messages.each do |storage_name, storage_settings| storage_settings['path'] = TestEnv.repos_path unless storage_settings.key?('path') - storage_settings['failure_count_threshold'] ||= 10 - storage_settings['failure_wait_time'] ||= 30 - storage_settings['failure_reset_time'] ||= 1800 - storage_settings['storage_timeout'] ||= 5 end allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index 78a2ff73746..5f22d886910 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -39,11 +39,11 @@ module StubGitlabCalls .and_return({ 'tags' => tags }) allow_any_instance_of(ContainerRegistry::Client) - .to receive(:repository_manifest).with(repository) + .to receive(:repository_manifest).with(repository, anything) .and_return(stub_container_registry_tag_manifest) allow_any_instance_of(ContainerRegistry::Client) - .to receive(:blob).with(repository) + .to receive(:blob).with(repository, anything, 'application/octet-stream') .and_return(stub_container_registry_blob) end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 6e5b9700b54..fff120fcb88 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,6 +17,7 @@ module TestEnv 'feature_conflict' => 'bb5206f', 'fix' => '48f0be4', 'improve/awesome' => '5937ac0', + 'merged-target' => '21751bf', 'markdown' => '0ed8c6c', 'lfs' => 'be93687', 'master' => 'b83d6e3', @@ -45,7 +46,8 @@ module TestEnv 'v1.1.0' => 'b83d6e3', 'add-ipython-files' => '93ee732', 'add-pdf-file' => 'e774ebd', - 'add-pdf-text-binary' => '79faa7b' + 'add-pdf-text-binary' => '79faa7b', + 'add_images_and_changes' => '010d106' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily @@ -180,6 +182,8 @@ module TestEnv return unless @gitaly_pid Process.kill('KILL', @gitaly_pid) + rescue Errno::ESRCH + # The process can already be gone if the test run was INTerrupted. end def setup_factory_repo @@ -306,6 +310,9 @@ module TestEnv ensure_component_dir_name_is_correct!(component, install_dir) + # On CI, once installed, components never need update + return if File.exist?(install_dir) && ENV['CI'] + if component_needs_update?(install_dir, version) # Cleanup the component entirely to ensure we start fresh FileUtils.rm_rf(install_dir) diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb index 0fa74f911f6..909d4e2ee8d 100644 --- a/spec/support/time_tracking_shared_examples.rb +++ b/spec/support/time_tracking_shared_examples.rb @@ -80,6 +80,6 @@ end def submit_time(quick_action) fill_in 'note[note]', with: quick_action - find('.js-comment-submit-button').trigger('click') + find('.js-comment-submit-button').click wait_for_requests end diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb index 2dfa5fbecea..3d9705c9c05 100644 --- a/spec/support/unique_ip_check_shared_examples.rb +++ b/spec/support/unique_ip_check_shared_examples.rb @@ -56,13 +56,13 @@ shared_examples 'user login request with unique ip limit' do |success_status = 2 end it 'allows user authenticating from the same ip' do - expect(request_from_ip('ip')).to have_http_status(success_status) - expect(request_from_ip('ip')).to have_http_status(success_status) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) end it 'blocks user authenticating from two distinct ips' do - expect(request_from_ip('ip')).to have_http_status(success_status) - expect(request_from_ip('ip2')).to have_http_status(403) + expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) + expect(request_from_ip('ip2')).to have_gitlab_http_status(403) end end end diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb index b5c3c0f55b8..f4130d68271 100644 --- a/spec/support/wait_for_requests.rb +++ b/spec/support/wait_for_requests.rb @@ -1,25 +1,47 @@ -require_relative './wait_for_requests' - module WaitForRequests extend self # This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests def block_and_wait_for_requests_complete + block_requests { wait_for_all_requests } + end + + # Block all requests inside block with 503 response + def block_requests Gitlab::Testing::RequestBlockerMiddleware.block_requests! - wait_for('pending requests complete') do - Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? && finished_all_requests? - end + yield ensure Gitlab::Testing::RequestBlockerMiddleware.allow_requests! end + # Slow down requests inside block by injecting `sleep 0.2` before each response + def slow_requests + Gitlab::Testing::RequestBlockerMiddleware.slow_requests! + yield + ensure + Gitlab::Testing::RequestBlockerMiddleware.allow_requests! + end + + # Wait for client-side AJAX requests def wait_for_requests - wait_for('JS requests') { finished_all_requests? } + wait_for('JS requests complete') { finished_all_js_requests? } + end + + # Wait for active Rack requests and client-side AJAX requests + def wait_for_all_requests + wait_for('pending requests complete') do + finished_all_rack_reqiests? && + finished_all_js_requests? + end end private - def finished_all_requests? + def finished_all_rack_reqiests? + Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? + end + + def finished_all_js_requests? return true unless javascript_test? finished_all_ajax_requests? && diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 0c8c8a2ab05..bf2e11bc360 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -4,7 +4,15 @@ require 'rake' describe 'gitlab:app namespace rake task' do let(:enable_registry) { true } - before :all do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + def backup_tar + tars_glob.first + end + + before(:all) do Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/shell' @@ -19,9 +27,16 @@ describe 'gitlab:app namespace rake task' do end before do + stub_env('force', 'yes') + FileUtils.rm(tars_glob, force: true) + reenable_backup_sub_tasks stub_container_registry_config(enabled: enable_registry) end + after do + FileUtils.rm(tars_glob, force: true) + end + def run_rake_task(task_name) Rake::Task[task_name].reenable Rake.application.invoke_task task_name @@ -34,22 +49,15 @@ describe 'gitlab:app namespace rake task' do end describe 'backup_restore' do - before do - # avoid writing task output to spec progress - allow($stdout).to receive :write - end - context 'gitlab version' do before do allow(Dir).to receive(:glob).and_return(['1_gitlab_backup.tar']) - allow(Dir).to receive(:chdir) allow(File).to receive(:exist?).and_return(true) allow(Kernel).to receive(:system).and_return(true) allow(FileUtils).to receive(:cp_r).and_return(true) allow(FileUtils).to receive(:mv).and_return(true) allow(Rake::Task["gitlab:shell:setup"]) .to receive(:invoke).and_return(true) - ENV['force'] = 'yes' end let(:gitlab_version) { Gitlab::VERSION } @@ -58,8 +66,9 @@ describe 'gitlab:app namespace rake task' do allow(YAML).to receive(:load_file) .and_return({ gitlab_version: "not #{gitlab_version}" }) - expect { run_rake_task('gitlab:backup:restore') } - .to raise_error(SystemExit) + expect do + expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout + end.to raise_error(SystemExit) end it 'invokes restoration on match' do @@ -75,44 +84,15 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) - expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error + expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout end end end # backup_restore task describe 'backup' do - before(:all) do - ENV['force'] = 'yes' - end - - def tars_glob - Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) - end - - def create_backup - FileUtils.rm tars_glob - + before do # This reconnect makes our project fixture disappear, breaking the restore. Stub it out. allow(ActiveRecord::Base.connection).to receive(:reconnect!) - - # Redirect STDOUT and run the rake task - orig_stdout = $stdout - $stdout = StringIO.new - reenable_backup_sub_tasks - run_rake_task('gitlab:backup:create') - reenable_backup_sub_tasks - $stdout = orig_stdout - - @backup_tar = tars_glob.first - end - - def restore_backup - orig_stdout = $stdout - $stdout = StringIO.new - reenable_backup_sub_tasks - run_rake_task('gitlab:backup:restore') - reenable_backup_sub_tasks - $stdout = orig_stdout end describe 'backup creation and deletion using custom_hooks' do @@ -120,27 +100,17 @@ describe 'gitlab:app namespace rake task' do let(:user_backup_path) { "repositories/#{project.disk_path}" } before do - @origin_cd = Dir.pwd - - path = File.join(project.repository.path_to_repo, filename) + stub_env('SKIP', 'db') + path = File.join(project.repository.path_to_repo, 'custom_hooks') FileUtils.mkdir_p(path) FileUtils.touch(File.join(path, "dummy.txt")) - - ENV["SKIP"] = "db" - create_backup - end - - after do - ENV["SKIP"] = "" - FileUtils.rm(@backup_tar) - Dir.chdir(@origin_cd) end context 'project uses custom_hooks and successfully creates backup' do - let(:filename) { "custom_hooks" } - it 'creates custom_hooks.tar and project bundle' do - tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}}) + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + + tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{backup_tar}}) expect(exit_status).to eq(0) expect(tar_contents).to match(user_backup_path) @@ -149,47 +119,43 @@ describe 'gitlab:app namespace rake task' do end it 'restores files correctly' do - restore_backup + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout - expect(Dir.entries(File.join(project.repository.path, "custom_hooks"))).to include("dummy.txt") + expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt") end end end context 'tar creation' do - before do - create_backup - end - - after do - FileUtils.rm(@backup_tar) - end - context 'archive file permissions' do it 'sets correct permissions on the tar file' do - expect(File.exist?(@backup_tar)).to be_truthy - expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600') + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + + expect(File.exist?(backup_tar)).to be_truthy + expect(File::Stat.new(backup_tar).mode.to_s(8)).to eq('100600') end context 'with custom archive_permissions' do before do allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651) - # We created a backup in a before(:all) so it got the default permissions. - # We now need to do some work to create a _new_ backup file using our stub. - FileUtils.rm(@backup_tar) - create_backup end it 'uses the custom permissions' do - expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651') + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + + expect(File::Stat.new(backup_tar).mode.to_s(8)).to eq('100651') end end end it 'sets correct permissions on the tar contents' do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} + %W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} ) + expect(exit_status).to eq(0) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') @@ -203,6 +169,8 @@ describe 'gitlab:app namespace rake task' do end it 'deletes temp directories' do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + temp_dirs = Dir.glob( File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,pages,lfs,registry}') ) @@ -214,9 +182,12 @@ describe 'gitlab:app namespace rake task' do let(:enable_registry) { false } it 'does not create registry.tar.gz' do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar}} + %W{tar -tvf #{backup_tar}} ) + expect(exit_status).to eq(0) expect(tar_contents).not_to match('registry.tar.gz') end @@ -224,42 +195,41 @@ describe 'gitlab:app namespace rake task' do end context 'multiple repository storages' do - let(:project_a) { create(:project, :repository, repository_storage: 'default') } - let(:project_b) { create(:project, :repository, repository_storage: 'custom') } - - before do - FileUtils.mkdir('tmp/tests/default_storage') - FileUtils.mkdir('tmp/tests/custom_storage') - gitaly_address = Gitlab.config.repositories.storages.default.gitaly_address - storages = { + let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address } + let(:storages) do + { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address }, - 'custom' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address } + 'test_second_storage' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address } } - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end - # Create the projects now, after mocking the settings but before doing the backup - project_a - project_b + before do + # We only need a backup of the repositories for this test + stub_env('SKIP', 'db,uploads,builds,artifacts,lfs,registry') + FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) + FileUtils.mkdir(Settings.absolute('tmp/tests/custom_storage')) + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) # Avoid asking gitaly about the root ref (which will fail beacuse of the # mocked storages) allow_any_instance_of(Repository).to receive(:empty_repo?).and_return(false) - - # We only need a backup of the repositories for this test - ENV["SKIP"] = "db,uploads,builds,artifacts,lfs,registry" - create_backup end after do - FileUtils.rm_rf('tmp/tests/default_storage') - FileUtils.rm_rf('tmp/tests/custom_storage') - FileUtils.rm(@backup_tar) + FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage')) + FileUtils.rm_rf(Settings.absolute('tmp/tests/custom_storage')) end it 'includes repositories in all repository storages' do + project_a = create(:project, :repository, repository_storage: 'default') + project_b = create(:project, :repository, repository_storage: 'test_second_storage') + + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} repositories} + %W{tar -tvf #{backup_tar} repositories} ) + expect(exit_status).to eq(0) expect(tar_contents).to match("repositories/#{project_a.disk_path}.bundle") expect(tar_contents).to match("repositories/#{project_b.disk_path}.bundle") @@ -268,35 +238,15 @@ describe 'gitlab:app namespace rake task' do end # backup_create task describe "Skipping items" do - def tars_glob - Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) - end - - before :all do - @origin_cd = Dir.pwd - - reenable_backup_sub_tasks - - FileUtils.rm tars_glob - - # Redirect STDOUT and run the rake task - orig_stdout = $stdout - $stdout = StringIO.new - ENV["SKIP"] = "repositories,uploads" - run_rake_task('gitlab:backup:create') - $stdout = orig_stdout - - @backup_tar = tars_glob.first - end - - after :all do - FileUtils.rm(@backup_tar) - Dir.chdir @origin_cd + before do + stub_env('SKIP', 'repositories,uploads') end it "does not contain skipped item" do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} + %W{tar -tvf #{backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz pages.tar.gz lfs.tar.gz registry.tar.gz} ) expect(tar_contents).to match('db/') @@ -310,9 +260,10 @@ describe 'gitlab:app namespace rake task' do end it 'does not invoke repositories restore' do + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + allow(Rake::Task['gitlab:shell:setup']) .to receive(:invoke).and_return(true) - allow($stdout).to receive :write expect(Rake::Task['gitlab:db:drop_tables']).to receive :invoke expect(Rake::Task['gitlab:backup:db:restore']).to receive :invoke @@ -324,38 +275,15 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke expect(Rake::Task['gitlab:shell:setup']).to receive :invoke - expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error + expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout end end describe "Human Readable Backup Name" do - def tars_glob - Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) - end - - before :all do - @origin_cd = Dir.pwd - - reenable_backup_sub_tasks - - FileUtils.rm tars_glob - - # Redirect STDOUT and run the rake task - orig_stdout = $stdout - $stdout = StringIO.new - run_rake_task('gitlab:backup:create') - $stdout = orig_stdout - - @backup_tar = tars_glob.first - end - - after :all do - FileUtils.rm(@backup_tar) - Dir.chdir @origin_cd - end - it 'name has human readable time' do - expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_\d+\.\d+\.\d+.*_gitlab_backup.tar$/) + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout + + expect(backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_\d+\.\d+\.\d+.*_gitlab_backup.tar$/) end end end # gitlab:app namespace diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index 1e9b20435ec..5dd8fe8eaa5 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -43,15 +43,8 @@ describe 'gitlab:gitaly namespace rake task' do describe 'gmake/make' do let(:command_preamble) { %w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] } - before(:all) do - @old_env_ci = ENV.delete('CI') - end - - after(:all) do - ENV['CI'] = @old_env_ci if @old_env_ci - end - before do + stub_env('CI', false) FileUtils.mkdir_p(clone_path) expect(Dir).to receive(:chdir).with(clone_path).and_call_original allow(Bundler).to receive(:bundle_path).and_return('/fake/bundle_path') diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb index 12d442b9820..279234f2887 100644 --- a/spec/tasks/gitlab/ldap_rake_spec.rb +++ b/spec/tasks/gitlab/ldap_rake_spec.rb @@ -4,7 +4,7 @@ describe 'gitlab:ldap:rename_provider rake task' do it 'completes without error' do Rake.application.rake_require 'tasks/gitlab/ldap' stub_warn_user_is_not_gitlab - ENV['force'] = 'yes' + stub_env('force', 'yes') create(:identity) # Necessary to prevent `exit 1` from the task. diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb new file mode 100644 index 00000000000..f59792c3d36 --- /dev/null +++ b/spec/tasks/gitlab/storage_rake_spec.rb @@ -0,0 +1,52 @@ +require 'rake_helper' + +describe 'gitlab:storage rake tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/storage' + + stub_warn_user_is_not_gitlab + end + + describe 'migrate_to_hashed rake task' do + context '0 legacy projects' do + it 'does nothing' do + expect(StorageMigratorWorker).not_to receive(:perform_async) + + run_rake_task('gitlab:storage:migrate_to_hashed') + end + end + + context '5 legacy projects' do + let(:projects) { create_list(:project, 5, storage_version: 0) } + + context 'in batches of 1' do + before do + stub_env('BATCH' => 1) + end + + it 'enqueues one StorageMigratorWorker per project' do + projects.each do |project| + expect(StorageMigratorWorker).to receive(:perform_async).with(project.id, project.id) + end + + run_rake_task('gitlab:storage:migrate_to_hashed') + end + end + + context 'in batches of 2' do + before do + stub_env('BATCH' => 2) + end + + it 'enqueues one StorageMigratorWorker per 2 projects' do + projects.map(&:id).sort.each_slice(2) do |first, last| + last ||= first + expect(StorageMigratorWorker).to receive(:perform_async).with(first, last) + end + + run_rake_task('gitlab:storage:migrate_to_hashed') + end + end + end + end +end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb deleted file mode 100644 index 972670e7f91..00000000000 --- a/spec/tasks/gitlab/users_rake_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'spec_helper' -require 'rake' - -describe 'gitlab:users namespace rake task' do - let(:enable_registry) { true } - - before :all do - Rake.application.rake_require 'tasks/gitlab/helpers' - Rake.application.rake_require 'tasks/gitlab/users' - - # empty task as env is already loaded - Rake::Task.define_task :environment - end - - def run_rake_task(task_name) - Rake::Task[task_name].reenable - Rake.application.invoke_task task_name - end - - describe 'clear_all_authentication_tokens' do - before do - # avoid writing task output to spec progress - allow($stdout).to receive :write - end - - context 'gitlab version' do - it 'clears the authentication token for all users' do - create_list(:user, 2) - - expect(User.pluck(:authentication_token)).to all(be_present) - - run_rake_task('gitlab:users:clear_all_authentication_tokens') - - expect(User.pluck(:authentication_token)).to all(be_nil) - end - end - end -end diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb index b84137eb365..51f7a536cbb 100644 --- a/spec/tasks/tokens_spec.rb +++ b/spec/tasks/tokens_spec.rb @@ -7,12 +7,6 @@ describe 'tokens rake tasks' do Rake.application.rake_require 'tasks/tokens' end - describe 'reset_all task' do - it 'invokes create_hooks task' do - expect { run_rake_task('tokens:reset_all_auth') }.to change { user.reload.authentication_token } - end - end - describe 'reset_all_email task' do it 'invokes create_hooks task' do expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 2492d56a5cf..f52b2bab05b 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -3,25 +3,51 @@ require 'spec_helper' describe FileUploader do let(:uploader) { described_class.new(build_stubbed(:project)) } - describe '.absolute_path' do - it 'returns the correct absolute path by building it dynamically' do - project = build_stubbed(:project) - upload = double(model: project, path: 'secret/foo.jpg') + context 'legacy storage' do + let(:project) { build_stubbed(:project) } - dynamic_segment = project.path_with_namespace + describe '.absolute_path' do + it 'returns the correct absolute path by building it dynamically' do + upload = double(model: project, path: 'secret/foo.jpg') - expect(described_class.absolute_path(upload)) - .to end_with("#{dynamic_segment}/secret/foo.jpg") + dynamic_segment = project.full_path + + expect(described_class.absolute_path(upload)) + .to end_with("#{dynamic_segment}/secret/foo.jpg") + end + end + + describe "#store_dir" do + it "stores in the namespace path" do + uploader = described_class.new(project) + + expect(uploader.store_dir).to include(project.full_path) + expect(uploader.store_dir).not_to include("system") + end end end - describe "#store_dir" do - it "stores in the namespace path" do - project = build_stubbed(:project) - uploader = described_class.new(project) + context 'hashed storage' do + let(:project) { build_stubbed(:project, :hashed) } + + describe '.absolute_path' do + it 'returns the correct absolute path by building it dynamically' do + upload = double(model: project, path: 'secret/foo.jpg') + + dynamic_segment = project.disk_path + + expect(described_class.absolute_path(upload)) + .to end_with("#{dynamic_segment}/secret/foo.jpg") + end + end + + describe "#store_dir" do + it "stores in the namespace path" do + uploader = described_class.new(project) - expect(uploader.store_dir).to include(project.path_with_namespace) - expect(uploader.store_dir).not_to include("system") + expect(uploader.store_dir).to include(project.disk_path) + expect(uploader.store_dir).not_to include("system") + end end end diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb deleted file mode 100644 index 08e1c5a728a..00000000000 --- a/spec/validators/dynamic_path_validator_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe DynamicPathValidator do - let(:validator) { described_class.new(attributes: [:path]) } - - def expect_handles_invalid_utf8 - expect { yield('\255invalid') }.to be_falsey - end - - describe '.valid_user_path' do - it 'handles invalid utf8' do - expect(described_class.valid_user_path?("a\0weird\255path")).to be_falsey - end - end - - describe '.valid_group_path' do - it 'handles invalid utf8' do - expect(described_class.valid_group_path?("a\0weird\255path")).to be_falsey - end - end - - describe '.valid_project_path' do - it 'handles invalid utf8' do - expect(described_class.valid_project_path?("a\0weird\255path")).to be_falsey - end - end - - describe '#path_valid_for_record?' do - context 'for project' do - it 'calls valid_project_path?' do - project = build(:project, path: 'activity') - - expect(described_class).to receive(:valid_project_path?).with(project.full_path).and_call_original - - expect(validator.path_valid_for_record?(project, 'activity')).to be_truthy - end - end - - context 'for group' do - it 'calls valid_group_path?' do - group = build(:group, :nested, path: 'activity') - - expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original - - expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey - end - end - - context 'for user' do - it 'calls valid_user_path?' do - user = build(:user, username: 'activity') - - expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original - - expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy - end - end - - context 'for user namespace' do - it 'calls valid_user_path?' do - user = create(:user, username: 'activity') - namespace = user.namespace - - expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original - - expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy - end - end - end - - describe '#validates_each' do - it 'adds a message when the path is not in the correct format' do - group = build(:group) - - validator.validate_each(group, :path, "Path with spaces, and comma's!") - - expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message) - end - - it 'adds a message when the path is not in the correct format' do - group = build(:group, path: 'users') - - validator.validate_each(group, :path, 'users') - - expect(group.errors[:path]).to include('users is a reserved name') - end - - it 'updating to an invalid path is not allowed' do - project = create(:project) - project.path = 'update' - - validator.validate_each(project, :path, 'update') - - expect(project.errors[:path]).to include('update is a reserved name') - end - end -end diff --git a/spec/validators/namespace_path_validator_spec.rb b/spec/validators/namespace_path_validator_spec.rb new file mode 100644 index 00000000000..61e2845f35f --- /dev/null +++ b/spec/validators/namespace_path_validator_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe NamespacePathValidator do + let(:validator) { described_class.new(attributes: [:path]) } + + describe '.valid_path?' do + it 'handles invalid utf8' do + expect(described_class.valid_path?("a\0weird\255path")).to be_falsey + end + end + + describe '#validates_each' do + it 'adds a message when the path is not in the correct format' do + group = build(:group) + + validator.validate_each(group, :path, "Path with spaces, and comma's!") + + expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message) + end + + it 'adds a message when the path is reserved when creating' do + group = build(:group, path: 'help') + + validator.validate_each(group, :path, 'help') + + expect(group.errors[:path]).to include('help is a reserved name') + end + + it 'adds a message when the path is reserved when updating' do + group = create(:group) + group.path = 'help' + + validator.validate_each(group, :path, 'help') + + expect(group.errors[:path]).to include('help is a reserved name') + end + end +end diff --git a/spec/validators/project_path_validator_spec.rb b/spec/validators/project_path_validator_spec.rb new file mode 100644 index 00000000000..8bb5e72dc22 --- /dev/null +++ b/spec/validators/project_path_validator_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe ProjectPathValidator do + let(:validator) { described_class.new(attributes: [:path]) } + + describe '.valid_path?' do + it 'handles invalid utf8' do + expect(described_class.valid_path?("a\0weird\255path")).to be_falsey + end + end + + describe '#validates_each' do + it 'adds a message when the path is not in the correct format' do + project = build(:project) + + validator.validate_each(project, :path, "Path with spaces, and comma's!") + + expect(project.errors[:path]).to include(Gitlab::PathRegex.project_path_format_message) + end + + it 'adds a message when the path is reserved when creating' do + project = build(:project, path: 'blob') + + validator.validate_each(project, :path, 'blob') + + expect(project.errors[:path]).to include('blob is a reserved name') + end + + it 'adds a message when the path is reserved when updating' do + project = create(:project) + project.path = 'blob' + + validator.validate_each(project, :path, 'blob') + + expect(project.errors[:path]).to include('blob is a reserved name') + end + end +end diff --git a/spec/validators/user_path_validator_spec.rb b/spec/validators/user_path_validator_spec.rb new file mode 100644 index 00000000000..a46089cc24f --- /dev/null +++ b/spec/validators/user_path_validator_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe UserPathValidator do + let(:validator) { described_class.new(attributes: [:username]) } + + describe '.valid_path?' do + it 'handles invalid utf8' do + expect(described_class.valid_path?("a\0weird\255path")).to be_falsey + end + end + + describe '#validates_each' do + it 'adds a message when the path is not in the correct format' do + user = build(:user) + + validator.validate_each(user, :username, "Path with spaces, and comma's!") + + expect(user.errors[:username]).to include(Gitlab::PathRegex.namespace_format_message) + end + + it 'adds a message when the path is reserved when creating' do + user = build(:user, username: 'help') + + validator.validate_each(user, :username, 'help') + + expect(user.errors[:username]).to include('help is a reserved name') + end + + it 'adds a message when the path is reserved when updating' do + user = create(:user) + user.username = 'help' + + validator.validate_each(user, :username, 'help') + + expect(user.errors[:username]).to include('help is a reserved name') + end + end +end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index c030129559e..0a78606171d 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -25,6 +25,14 @@ describe 'help/index' do end end + describe 'instance configuration link' do + it 'is visible to guests' do + render + + expect(rendered).to have_link(nil, help_instance_configuration_url) + end + end + def stub_user(user = double) allow(view).to receive(:user_signed_in?).and_return(user) end diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb new file mode 100644 index 00000000000..f30b5881fde --- /dev/null +++ b/spec/views/help/instance_configuration.html.haml_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +describe 'help/instance_configuration' do + describe 'General Sections:' do + let(:instance_configuration) { build(:instance_configuration)} + let(:settings) { instance_configuration.settings } + let(:ssh_settings) { settings[:ssh_algorithms_hashes] } + + before do + assign(:instance_configuration, instance_configuration) + end + + it 'has links to several sections' do + render + + expect(rendered).to have_link(nil, '#ssh-host-keys-fingerprints') if ssh_settings.any? + expect(rendered).to have_link(nil, '#gitlab-pages') + expect(rendered).to have_link(nil, '#gitlab-ci') + end + + it 'has several sections' do + render + + expect(rendered).to have_css('h2#ssh-host-keys-fingerprints') if ssh_settings.any? + expect(rendered).to have_css('h2#gitlab-pages') + expect(rendered).to have_css('h2#gitlab-ci') + end + end +end diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb index 98c7de9b709..efed2e02a1b 100644 --- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe 'projects/merge_requests/_commits.html.haml' do include Devise::Test::ControllerHelpers + include ProjectForksHelper let(:user) { create(:user) } - let(:target_project) { create(:project, :repository) } - let(:source_project) { create(:project, :repository, forked_from_project: target_project) } + let(:target_project) { create(:project, :public, :repository) } + let(:source_project) { fork_project(target_project, user, repository: true) } let(:merge_request) do create(:merge_request, :simple, diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 69c7d0cbf28..9b74a7e1946 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -2,16 +2,19 @@ require 'spec_helper' describe 'projects/merge_requests/edit.html.haml' do include Devise::Test::ControllerHelpers + include ProjectForksHelper let(:user) { create(:user) } let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } - let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:forked_project) { fork_project(project, user, repository: true) } + let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) } let(:milestone) { create(:milestone, project: project) } let(:closed_merge_request) do + project.add_developer(user) + create(:closed_merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project, author: user, assignee: user, diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 6f29d12373a..28d54c2fb77 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -2,16 +2,17 @@ require 'spec_helper' describe 'projects/merge_requests/show.html.haml' do include Devise::Test::ControllerHelpers + include ProjectForksHelper let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:fork_project) { create(:project, :repository, forked_from_project: project) } - let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:project) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(project, user, repository: true) } + let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) } let(:note) { create(:note_on_merge_request, project: project, noteable: closed_merge_request) } let(:closed_merge_request) do create(:closed_merge_request, - source_project: fork_project, + source_project: forked_project, target_project: project, author: user) end @@ -52,7 +53,7 @@ describe 'projects/merge_requests/show.html.haml' do context 'when the merge request is open' do it 'closes the merge request if the source project does not exist' do closed_merge_request.update_attributes(state: 'open') - fork_project.destroy + forked_project.destroy render diff --git a/spec/views/projects/registry/repositories/index.html.haml_spec.rb b/spec/views/projects/registry/repositories/index.html.haml_spec.rb deleted file mode 100644 index cf0aa44a4a2..00000000000 --- a/spec/views/projects/registry/repositories/index.html.haml_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe 'projects/registry/repositories/index' do - let(:group) { create(:group, path: 'group') } - let(:project) { create(:project, group: group, path: 'test') } - - let(:repository) do - create(:container_repository, project: project, name: 'image') - end - - before do - stub_container_registry_config(enabled: true, - host_port: 'registry.gitlab', - api_url: 'http://registry.gitlab') - - stub_container_registry_tags(repository: :any, tags: [:latest]) - - assign(:project, project) - assign(:images, [repository]) - - allow(view).to receive(:can?).and_return(true) - end - - it 'contains container repository path' do - render - - expect(rendered).to have_content 'group/test/image' - end - - it 'contains attribute for copying tag location into clipboard' do - render - - expect(rendered).to have_css 'button[data-clipboard-text="docker pull ' \ - 'registry.gitlab/group/test/image:latest"]' - end -end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index 8cc3f37ebe8..1a7ffd5cdbf 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -11,6 +11,8 @@ describe BuildFinishedWorker do expect(BuildHooksWorker) .to receive(:new).ordered.and_call_original + expect(BuildTraceSectionsWorker) + .to receive(:perform_async) expect_any_instance_of(BuildCoverageWorker) .to receive(:perform) expect_any_instance_of(BuildHooksWorker) diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb new file mode 100644 index 00000000000..45243f45547 --- /dev/null +++ b/spec/workers/build_trace_sections_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe BuildTraceSectionsWorker do + describe '#perform' do + context 'when build exists' do + let!(:build) { create(:ci_build) } + + it 'updates trace sections' do + expect_any_instance_of(Ci::Build) + .to receive(:parse_trace_sections!) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb new file mode 100644 index 00000000000..11f208289db --- /dev/null +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe ClusterProvisionWorker do + describe '#perform' do + context 'when cluster exists' do + let(:cluster) { create(:gcp_cluster) } + + it 'provision a cluster' do + expect_any_instance_of(Ci::ProvisionClusterService).to receive(:execute) + + described_class.new.perform(cluster.id) + end + end + + context 'when cluster does not exist' do + it 'does not provision a cluster' do + expect_any_instance_of(Ci::ProvisionClusterService).not_to receive(:execute) + + described_class.new.perform(123) + end + end + end +end diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb new file mode 100644 index 00000000000..1050651fa51 --- /dev/null +++ b/spec/workers/concerns/cluster_queue_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe ClusterQueue do + let(:worker) do + Class.new do + include Sidekiq::Worker + include ClusterQueue + end + end + + it 'sets a default pipelines queue automatically' do + expect(worker.sidekiq_options['queue']) + .to eq :gcp_cluster + end +end diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index f7b67b8efc6..47297de738b 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -32,7 +32,7 @@ describe GitGarbageCollectWorker do expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original - expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original + expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original subject.perform(project.id, :gc, lease_key, lease_uuid) end @@ -47,7 +47,6 @@ describe GitGarbageCollectWorker do expect(subject).not_to receive(:command) expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original - expect_any_instance_of(Repository).not_to receive(:branch_count).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original subject.perform(project.id, :gc, lease_key, lease_uuid) @@ -78,7 +77,7 @@ describe GitGarbageCollectWorker do expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original - expect_any_instance_of(Gitlab::Git::Repository).to receive(:branch_count).and_call_original + expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original subject.perform(project.id) end @@ -93,7 +92,6 @@ describe GitGarbageCollectWorker do expect(subject).not_to receive(:command) expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original - expect_any_instance_of(Repository).not_to receive(:branch_count).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original subject.perform(project.id) @@ -106,7 +104,7 @@ describe GitGarbageCollectWorker do it_should_behave_like 'flushing ref caches', true end - context "with Gitaly turned off", skip_gitaly_mock: true do + context "with Gitaly turned off", :skip_gitaly_mock do it_should_behave_like 'flushing ref caches', false end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 20cf580af8a..ed8cedc0079 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe NamespacelessProjectDestroyWorker do + include ProjectForksHelper + subject { described_class.new } before do @@ -55,9 +57,11 @@ describe NamespacelessProjectDestroyWorker do context 'project forked from another' do let!(:parent_project) { create(:project) } - - before do - create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) + let(:project) do + namespaceless_project = fork_project(parent_project) + namespaceless_project.namespace_id = nil + namespaceless_project.save(validate: false) + namespaceless_project end it 'closes open merge requests' do diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index d3707a3cc11..05eecf5f0bb 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -70,12 +70,15 @@ describe PostReceive do context "creates a Ci::Pipeline for every change" do before do - allow_any_instance_of(Ci::CreatePipelineService).to receive(:commit) do - OpenStruct.new(id: '123456') - end - allow_any_instance_of(Ci::CreatePipelineService).to receive(:branch?).and_return(true) - allow_any_instance_of(Repository).to receive(:ref_exists?).and_return(true) stub_ci_pipeline_to_return_yaml_file + + # TODO, don't stub private methods + # + allow_any_instance_of(Ci::CreatePipelineService) + .to receive(:commit).and_return(OpenStruct.new(id: '123456')) + + allow_any_instance_of(Repository) + .to receive(:branch_exists?).and_return(true) end it { expect { subject }.to change { Ci::Pipeline.count }.by(2) } diff --git a/spec/workers/project_migrate_hashed_storage_worker_spec.rb b/spec/workers/project_migrate_hashed_storage_worker_spec.rb new file mode 100644 index 00000000000..f5226dee0ad --- /dev/null +++ b/spec/workers/project_migrate_hashed_storage_worker_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe ProjectMigrateHashedStorageWorker do + describe '#perform' do + let(:project) { create(:project, :empty_repo) } + let(:pending_delete_project) { create(:project, :empty_repo, pending_delete: true) } + + it 'skips when project no longer exists' do + nonexistent_id = 999999999999 + + expect(::Projects::HashedStorageMigrationService).not_to receive(:new) + subject.perform(nonexistent_id) + end + + it 'skips when project is pending delete' do + expect(::Projects::HashedStorageMigrationService).not_to receive(:new) + + subject.perform(pending_delete_project.id) + end + + it 'delegates removal to service class' do + service = double('service') + expect(::Projects::HashedStorageMigrationService).to receive(:new).with(project, subject.logger).and_return(service) + expect(service).to receive(:execute) + + subject.perform(project.id) + end + end +end diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index d2609d21546..1d9bbf2ca62 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -69,7 +69,12 @@ describe RepositoryCheck::SingleRepositoryWorker do end def break_wiki(project) - FileUtils.rm_rf(wiki_path(project) + '/objects') + objects_dir = wiki_path(project) + '/objects' + + # Replace the /objects directory with a file so that the repo is + # invalid, _and_ 'git init' cannot fix it. + FileUtils.rm_rf(objects_dir) + FileUtils.touch(objects_dir) if File.directory?(wiki_path(project)) end def wiki_path(project) diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index d9e9409840f..e881ec37ae5 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,6 +12,28 @@ describe RepositoryForkWorker do end describe "#perform" do + describe 'when a worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :repository, :import_started) } + + it 'creates a new repository from a fork' do + allow(subject).to receive(:jid).and_return(jid) + + expect(shell).to receive(:fork_repository).with( + '/test/path', + project.full_path, + project.repository_storage_path, + fork_project.namespace.full_path + ).and_return(true) + + subject.perform( + project.id, + '/test/path', + project.full_path, + fork_project.namespace.full_path) + end + end + it "creates a new repository from a fork" do expect(shell).to receive(:fork_repository).with( '/test/path', diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 100dfc32bbe..5cff5108477 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -6,6 +6,23 @@ describe RepositoryImportWorker do subject { described_class.new } describe '#perform' do + context 'when worker was reset without cleanup' do + let(:jid) { '12345678' } + let(:started_project) { create(:project, :import_started, import_jid: jid) } + + it 'imports the project successfully' do + allow(subject).to receive(:jid).and_return(jid) + + expect_any_instance_of(Projects::ImportService).to receive(:execute) + .and_return({ status: :ok }) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches) + expect_any_instance_of(Project).to receive(:import_finish) + + subject.perform(project.id) + end + end + context 'when the import was successful' do it 'imports a project' do expect_any_instance_of(Projects::ImportService).to receive(:execute) diff --git a/spec/workers/storage_migrator_worker_spec.rb b/spec/workers/storage_migrator_worker_spec.rb new file mode 100644 index 00000000000..8619ff2f7da --- /dev/null +++ b/spec/workers/storage_migrator_worker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe StorageMigratorWorker do + subject(:worker) { described_class.new } + let(:projects) { create_list(:project, 2) } + + describe '#perform' do + let(:ids) { projects.map(&:id) } + + it 'enqueue jobs to ProjectMigrateHashedStorageWorker' do + expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice + + worker.perform(ids.min, ids.max) + end + + it 'sets projects as read only' do + allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice + worker.perform(ids.min, ids.max) + + projects.each do |project| + expect(project.reload.repository_read_only?).to be_truthy + end + end + + it 'rescues and log exceptions' do + allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError) + expect { worker.perform(ids.min, ids.max) }.not_to raise_error + end + end +end diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb index a5ad78393c9..f8b55e873df 100644 --- a/spec/workers/stuck_merge_jobs_worker_spec.rb +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -12,8 +12,13 @@ describe StuckMergeJobsWorker do worker.perform - expect(mr_with_sha.reload).to be_merged - expect(mr_without_sha.reload).to be_opened + mr_with_sha.reload + mr_without_sha.reload + + expect(mr_with_sha).to be_merged + expect(mr_without_sha).to be_opened + expect(mr_with_sha.merge_jid).to be_present + expect(mr_without_sha.merge_jid).to be_nil end it 'updates merge request to opened when locked but has not been merged' do diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb new file mode 100644 index 00000000000..dcd4a3b9aec --- /dev/null +++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe WaitForClusterCreationWorker do + describe '#perform' do + context 'when cluster exists' do + let(:cluster) { create(:gcp_cluster) } + let(:operation) { double } + + before do + allow(operation).to receive(:status).and_return(status) + allow(operation).to receive(:start_time).and_return(1.minute.ago) + allow(operation).to receive(:status_message).and_return('error') + allow_any_instance_of(Ci::FetchGcpOperationService).to receive(:execute).and_yield(operation) + end + + context 'when operation status is RUNNING' do + let(:status) { 'RUNNING' } + + it 'reschedules worker' do + expect(described_class).to receive(:perform_in) + + described_class.new.perform(cluster.id) + end + + context 'when operation timeout' do + before do + allow(operation).to receive(:start_time).and_return(30.minutes.ago.utc) + end + + it 'sets an error message on cluster' do + described_class.new.perform(cluster.id) + + expect(cluster.reload).to be_errored + end + end + end + + context 'when operation status is DONE' do + let(:status) { 'DONE' } + + it 'finalizes cluster creation' do + expect_any_instance_of(Ci::FinalizeClusterCreationService).to receive(:execute) + + described_class.new.perform(cluster.id) + end + end + + context 'when operation status is others' do + let(:status) { 'others' } + + it 'sets an error message on cluster' do + described_class.new.perform(cluster.id) + + expect(cluster.reload).to be_errored + end + end + end + + context 'when cluster does not exist' do + it 'does not provision a cluster' do + expect_any_instance_of(Ci::FetchGcpOperationService).not_to receive(:execute) + + described_class.new.perform(1234) + end + end + end +end |