diff options
author | Mike Greiling <mike@pixelcog.com> | 2017-11-02 17:05:06 -0500 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2017-11-02 17:05:06 -0500 |
commit | 11c21e953fe1d8aa108c7558d715300e3221a308 (patch) | |
tree | 46a55d65375e712f4ef59cead95c07179fbfefe3 /spec | |
parent | 2b29a4675bbaf79ed463e5ffc059e81aeb3251a8 (diff) | |
parent | 1d4efeec2e1a203be113077e6504d8f256271db1 (diff) | |
download | gitlab-ce-11c21e953fe1d8aa108c7558d715300e3221a308.tar.gz |
Merge branch 'master' into sh-headless-chrome-support
* master: (109 commits)
Remove Filesystem check metrics that use too much CPU to handle requests
Set merge_request_diff_id on MR when creating
Add a column linking an MR to its diff
Remove useless closeReopenReport specs
Clarify external artifacts only working when GitLab pages is enabled
Send SIGSTP before SIGTERM to actually give Sidekiq jobs 30s to finish when the memory killer kicks in
Remove an exception from the git user default SSH config check
Geo route whitelisting is too optimistic
Update .nvmrc to current stable (v9.0.0)
Update documentation
Address Douwe's feedback
Refactor responsive table styles to support nested error block
Add changelog items
Update specs for sudo behavior
Move RSS and incoming email tokens from User Settings > Accounts to User Settings > Access Tokens
Remove user authentication_token column
Migrate user private tokens to personal access tokens
Add sudo API scope
Consistently use PersonalAccessToken instead of PersonalToken
Remove User#private_token
...
Diffstat (limited to 'spec')
100 files changed, 1526 insertions, 994 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 6802b839eaa..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_gitlab_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_gitlab_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_gitlab_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_gitlab_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,11 +118,15 @@ 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_gitlab_http_status 200 end @@ -164,7 +134,7 @@ describe ApplicationController do 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_gitlab_http_status 404 end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 7b0976e3e67..4aed2a25baa 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/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index aecdfb50759..8016176110e 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -248,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) } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 52ef8c6a589..14021b8ca50 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -186,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) @@ -204,6 +210,20 @@ 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(:forked_project) { fork_project_with_submodules(project) } @@ -214,13 +234,7 @@ describe Projects::MergeRequestsController do 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 @@ -229,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' @@ -244,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 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/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb index 464d964c90e..86f90e51d6b 100644 --- a/spec/features/issues/filtered_search/recent_searches_spec.rb +++ b/spec/features/issues/filtered_search/recent_searches_spec.rb @@ -27,9 +27,8 @@ describe 'Recent searches', :js 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 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 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 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,8 +76,7 @@ describe 'Recent searches', :js do set_recent_searches(project_1_local_storage_key, '["foo", "bar"]') visit project_issues_path(project_1) - find('.filtered-search-history-dropdown-toggle-button').click - all('.filtered-search-history-dropdown-item')[0].click + all('.filtered-search-history-dropdown-item', visible: false, count: 2)[0].click wait_for_filtered_search('foo') expect(find('.filtered-search').value.strip).to eq('foo') @@ -91,14 +86,10 @@ describe 'Recent searches', :js do set_recent_searches(project_1_local_storage_key, '["foo"]') visit project_issues_path(project_1) - find('.filtered-search-history-dropdown-toggle-button').click - items_before = all('.filtered-search-history-dropdown-item') + all('.filtered-search-history-dropdown-item', visible: false, count: 1) - expect(items_before.count).to eq(1) - - find('.filtered-search-history-clear-button').click - find('.filtered-search-history-dropdown-toggle-button').click - items_after = all('.filtered-search-history-dropdown-item') + find('.filtered-search-history-clear-button', visible: false).click + items_after = all('.filtered-search-history-dropdown-item', visible: false, count: 0) expect(items_after.count).to eq(0) end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index b6da66ea901..a5c24b362a2 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -583,6 +583,16 @@ describe 'Issues' do expect(page.find_field("issue_description").value).not_to match /\n\n$/ end + + it "cancels a file upload correctly" do + dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false) + + click_button 'Cancel' + + 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 diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb index bac56270362..bd47599c2f9 100644 --- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb +++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb @@ -83,7 +83,7 @@ feature 'Mini Pipeline Graph', :js do end before do - toggle.click + toggle.trigger('click') wait_for_requests end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 1cddd35fd8a..0166ab8be99 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 @@ -56,47 +56,38 @@ describe 'Profile account page' do end end - describe 'when I reset private token' do - before do - visit profile_account_path - end - - it 'resets private token' do - previous_token = find("#private-token").value - - click_link('Reset private token') - - expect(find('#private-token').value).not_to eq(previous_token) - 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') + 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') + 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/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index 807a2189cc4..6f6d562c7b6 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -18,7 +18,7 @@ feature 'Mini Pipeline Graph in Commit View', :js do expect(page).to have_selector('.mr-widget-pipeline-graph') - first('.mini-pipeline-graph-dropdown-toggle').click + first('.mini-pipeline-graph-dropdown-toggle').trigger('click') wait_for_requests diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 59e32e169ec..b8fa1a54c24 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -67,7 +67,7 @@ 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 @@ -86,8 +86,8 @@ 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 @@ -105,8 +105,8 @@ 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 @@ -124,8 +124,8 @@ 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 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 7d07029ad9b..ff325aeadd3 100644 --- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb @@ -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) - - allow_any_instance_of(Gollum::Wiki).to receive(:file).with('image.jpg', 'master').and_return(file) - allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return('image/jpeg') + 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(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") 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/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/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/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/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js index cd19a0fae1e..59d4f7c45c6 100644 --- a/spec/javascripts/groups/components/app_spec.js +++ b/spec/javascripts/groups/components/app_spec.js @@ -431,9 +431,9 @@ describe('AppComponent', () => { }); it('should render groups tree', (done) => { - vm.groups = [mockParentGroupItem]; + vm.store.state.groups = [mockParentGroupItem]; vm.isLoading = false; - vm.pageInfo = mockPageInfo; + vm.store.state.pageInfo = mockPageInfo; Vue.nextTick(() => { expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined(); done(); 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/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 4e66304e0ad..a197b35f6fb 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -1,13 +1,12 @@ /* eslint-disable no-new */ import IssuableContext from '~/issuable_context'; -/* global LabelsSelect */ +import LabelsSelect from '~/labels_select'; import '~/gl_dropdown'; import 'select2'; import '~/api'; import '~/create_label'; import '~/users_select'; -import '~/labels_select'; (() => { let saveLabelCount = 0; 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/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/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js index 82f914b7c9d..979d2185076 100644 --- a/spec/javascripts/repo/components/repo_editor_spec.js +++ b/spec/javascripts/repo/components/repo_editor_spec.js @@ -17,6 +17,7 @@ describe('RepoEditor', () => { f.active = true; f.tempFile = true; vm.$store.state.openFiles.push(f); + vm.$store.getters.activeFile.html = 'testing'; vm.monaco = true; vm.$mount(); @@ -31,18 +32,25 @@ describe('RepoEditor', () => { it('renders an ide container', (done) => { Vue.nextTick(() => { expect(vm.shouldHideEditor).toBeFalsy(); + expect(vm.$el.textContent.trim()).toBe(''); + done(); }); }); describe('when open file is binary and not raw', () => { - it('does not render the IDE', (done) => { + beforeEach((done) => { vm.$store.getters.activeFile.binary = true; - Vue.nextTick(() => { - expect(vm.shouldHideEditor).toBeTruthy(); - done(); - }); + Vue.nextTick(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/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index cf811af3d6c..5e55a5d2686 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -3,7 +3,6 @@ import '~/gl_dropdown'; import '~/search_autocomplete'; import '~/lib/utils/common_utils'; -import 'vendor/fuzzaldrin-plus'; (function() { var assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; 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_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 ba303738f71..8762ce9903b 100644 --- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js +++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js @@ -11,63 +11,63 @@ describe('CI Badge Link Component', () => { 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', }, }; 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/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/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/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/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/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/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 412a0093d97..c04a9688503 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -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 @@ -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/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb new file mode 100644 index 00000000000..c2d2c6e1bc8 --- /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_return([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_return([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/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index b161d25b96c..bf6d199ebe2 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1163,6 +1163,7 @@ describe Gitlab::Git::Repository, seed_helper: true do 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 @@ -1184,6 +1185,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 @@ -1336,6 +1341,24 @@ describe Gitlab::Git::Repository, seed_helper: true do 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, '') @@ -1609,39 +1632,57 @@ describe Gitlab::Git::Repository, seed_helper: true do subject { repository.ff_merge(user, source_sha, target_branch) } - 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) + 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 + 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') } + 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) + it 'throws an ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end end - end - context 'with a non-existing source commit' do - let(:source_sha) { 'f001' } + context 'with a non-existing source commit' do + let(:source_sha) { 'f001' } - it 'throws an ArgumentError' do - expect { subject }.to raise_error(ArgumentError) + it 'throws an ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end end - end - context 'when the source sha is not a descendant of the branch head' do - let(:source_sha) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' } + 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) + 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) + 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 def create_remote_branch(repository, remote_name, branch_name, source_branch_name) diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index c0eac98d718..643a4b2d03e 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -2,53 +2,82 @@ 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) } before do - expect(Gitlab::Git::Env).to receive(:all).and_return({ + allow(Gitlab::Git::Env).to receive(:all).and_return({ GIT_OBJECT_DIRECTORY: 'foo', GIT_ALTERNATE_OBJECT_DIRECTORIES: 'bar' }) end - context "#new_refs" do - let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } + def stub_popen_rev_list(*additional_args, output:) + expect(rev_list).to receive(:popen).with([ + Gitlab.config.git.bin_path, + "--git-dir=#{project.repository.path_to_repo}", + 'rev-list', + *additional_args + ], + nil, + { + 'GIT_OBJECT_DIRECTORY' => 'foo', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' + }).and_return([output, 0]) + end + context "#new_refs" do 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' - ], - nil, - { - 'GIT_OBJECT_DIRECTORY' => 'foo', - 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' - }).and_return(["sha1\nsha2", 0]) + 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 return a lazy enumerator' do + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + + expect(rev_list.new_objects(lazy: true)).to be_a Enumerator::Lazy + 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.force).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/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index e144e28b5d8..d9ec28ab02e 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -89,4 +89,38 @@ describe Gitlab::GitalyClient::OperationService do 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/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 index 742a792a1af..86be06ff595 100644 --- a/spec/lib/gitlab/middleware/read_only_spec.rb +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -83,6 +83,13 @@ describe Gitlab::Middleware::ReadOnly do 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') @@ -104,6 +111,25 @@ describe Gitlab::Middleware::ReadOnly do 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 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/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 80bf7986ee0..249c77dc636 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -268,7 +268,8 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "project-#{project.id}", - RepoPath: repo_path + RepoPath: repo_path, + ShowAllRefs: false } end @@ -282,7 +283,8 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "wiki-#{project.id}", - RepoPath: repo_path + RepoPath: repo_path, + ShowAllRefs: false } end @@ -324,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 @@ -336,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 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 b4b83b70d1c..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 @@ -39,14 +39,6 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do it { is_expected.to eq(expected_result) } end - - it 'skips GitLab read-only instances' do - stub_user - stub_home_dir - allow(Gitlab::Database).to receive(:read_only?).and_return(true) - - is_expected.to be_truthy - end end describe '#check?' do 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/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/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/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 52a2b4b2850..3d46434fc27 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -156,36 +156,35 @@ describe ProjectWiki do 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') - .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') - .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.jpg') - expect(file.mime_type).to eq('image/jpeg') + 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 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 Gitlab::Git::WikiFile instance' do - file = subject.find_file('image.jpg') - expect(file).to be_a Gitlab::Git::WikiFile + context 'when Gitaly wiki_find_file is disabled', :skip_gitaly_mock do + it_behaves_like 'finding a wiki file' end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1c3c9068f12..fb03e320734 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -346,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 @@ -526,14 +525,6 @@ describe User do end end - describe 'authentication token' do - it "has authentication token" do - user = create(:user) - - expect(user.authentication_token).not_to be_blank - end - end - describe 'ensure incoming email token' do it 'has incoming email token' do user = create(:user) diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index de7ce848a31..308134eba72 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -25,7 +25,7 @@ describe 'doorkeeper access' do 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_gitlab_http_status(200) @@ -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_gitlab_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_gitlab_http_status(401) + expect(response).to have_gitlab_http_status(403) end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 9f3b5a809d7..6c0996c543d 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -28,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 @@ -69,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 @@ -160,75 +128,32 @@ describe API::Helpers do end end - describe "when authenticating using a user's private token" do - it "returns a 401 response 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 raise_error /401/ - end - - it "returns a 401 response 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 raise_error /401/ - end - - it 'returns a 401 response for a user who is blocked' do - user.block! - env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token - - expect { current_user }.to raise_error /401/ - 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 a 401 response for an invalid token" do env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token' expect { current_user }.to raise_error /401/ end - it "returns a 401 response 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 raise_error /401/ + expect { current_user }.to raise_error /403/ end - it 'returns a 401 response for a user who is blocked' do + 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 raise_error /401/ + 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 - - expect(current_user).to eq(user) end it "does not allow tokens without the appropriate scope" do @@ -252,210 +177,6 @@ describe API::Helpers do expect { current_user }.to raise_error API::APIGuard::ExpiredError 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 - - context 'when user is blocked' do - before do - user.block! - end - - it 'changes current_user to sudo' do - set_env(admin, user.id) - - expect(current_user).to eq(user) - 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 - end - end end describe '.handle_api_exception' do @@ -582,4 +303,147 @@ describe API::Helpers do end end end + + 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 + env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}" + end + + it_behaves_like 'sudo' + end + + 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 + + context 'passed as header' do + before do + env[API::APIGuard::PRIVATE_TOKEN_HEADER] = token.token + end + + it_behaves_like 'sudo' + end + end + + 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 end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb deleted file mode 100644 index 83d09878813..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_gitlab_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_gitlab_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_gitlab_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_gitlab_http_status(400) - end - - it "returns authentication error with username" do - post api("/session"), email: user.username - - expect(response).to have_gitlab_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_gitlab_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_gitlab_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_gitlab_http_status(401) - end - end - end -end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 4737f034f21..634c8dae0ba 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -127,8 +127,8 @@ describe API::Users do 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).token - get api("/users?private_token=#{admin_personal_access_token}&sudo=#{user.id}", admin) + 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 @@ -1097,14 +1097,6 @@ describe API::Users do 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_gitlab_http_status(403) - end - end - it 'returns current user without private token when sudo not defined' do get api("/user", user) @@ -1139,24 +1131,6 @@ describe API::Users do 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_gitlab_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_gitlab_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/admin') - expect(json_response['id']).to eq(admin.id) - end - end end context 'with unauthenticated user' do diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 407d19c3b2a..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 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/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb index 87832b3dca1..f9285049c0d 100644 --- a/spec/serializers/merge_request_entity_spec.rb +++ b/spec/serializers/merge_request_entity_spec.rb @@ -30,8 +30,17 @@ 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, 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/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/spec_helper.rb b/spec/spec_helper.rb index 97bd7257e47..7c8331f6c60 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -52,6 +52,7 @@ RSpec.configure do |config| 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 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/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 @@ +xK +0EgNI|ADt*^
mZ qGčY8ZK7"Fc%oHD9rZLsMJ2=ACmeFgVxI9H2XJrp6;N8z??>+zWƏBÞf}bN@K\SYiSC
\ 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/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 4aed40bf22d..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 diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index a27bfdee3d2..fff120fcb88 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -182,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 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 } |