diff options
Diffstat (limited to 'spec')
92 files changed, 1482 insertions, 307 deletions
diff --git a/spec/controllers/acme_challenges_controller_spec.rb b/spec/controllers/acme_challenges_controller_spec.rb new file mode 100644 index 00000000000..cee06bed27b --- /dev/null +++ b/spec/controllers/acme_challenges_controller_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AcmeChallengesController do + describe '#show' do + let!(:acme_order) { create(:pages_domain_acme_order) } + + def make_request(domain, token) + get(:show, params: { domain: domain, token: token }) + end + + before do + make_request(domain, token) + end + + context 'with right domain and token' do + let(:domain) { acme_order.pages_domain.domain } + let(:token) { acme_order.challenge_token } + + it 'renders acme challenge file content' do + expect(response.body).to eq(acme_order.challenge_file_content) + end + end + + context 'when domain is invalid' do + let(:domain) { 'somewrongdomain.com' } + let(:token) { acme_order.challenge_token } + + it 'renders not found' do + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when token is invalid' do + let(:domain) { acme_order.pages_domain.domain } + let(:token) { 'wrongtoken' } + + it 'renders not found' do + expect(response).to have_gitlab_http_status(404) + end + end + end +end diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb index f1e0923f316..f7c813576aa 100644 --- a/spec/controllers/import/fogbugz_controller_spec.rb +++ b/spec/controllers/import/fogbugz_controller_spec.rb @@ -11,6 +11,44 @@ describe Import::FogbugzController do sign_in(user) end + describe 'POST #callback' do + let(:token) { FFaker::Lorem.characters(8) } + let(:uri) { 'https://example.com' } + let(:xml_response) { %Q(<?xml version=\"1.0\" encoding=\"UTF-8\"?><response><token><![CDATA[#{token}]]></token></response>) } + + it 'attempts to contact Fogbugz server' do + stub_request(:post, "https://example.com/api.asp").to_return(status: 200, body: xml_response, headers: {}) + + post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' } + + expect(session[:fogbugz_token]).to eq(token) + expect(session[:fogbugz_uri]).to eq(uri) + expect(response).to redirect_to(new_user_map_import_fogbugz_path) + end + end + + describe 'POST #create_user_map' do + let(:user_map) do + { + "2" => { + "name" => "Test User", + "email" => "testuser@example.com", + "gitlab_user" => "3" + } + } + end + + it 'stores the user map in the session' do + client = double(user_map: {}) + expect(controller).to receive(:client).and_return(client) + + post :create_user_map, params: { users: user_map } + + expect(session[:fogbugz_user_map]).to eq(user_map) + expect(response).to redirect_to(status_import_fogbugz_path) + end + end + describe 'GET status' do before do @repo = OpenStruct.new(name: 'vim') diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index d5eea5b0439..9699f2952f2 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -433,20 +433,6 @@ describe Projects::EnvironmentsController do end context 'when only one time param is provided' do - context 'when :metrics_time_window feature flag is disabled' do - before do - stub_feature_flags(metrics_time_window: false) - expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil) - end - - it 'returns a time-window agnostic response' do - additional_metrics(start: '1552647300.651094') - - expect(response).to have_gitlab_http_status(204) - expect(json_response).to eq({}) - end - end - it 'raises an error when start is missing' do expect { additional_metrics(end: '1552647300.651094') } .to raise_error(ActionController::ParameterMissing) diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index b91a4df40a5..117b9cf7915 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -200,6 +200,21 @@ describe Projects::Settings::CiCdController do expect(response).to redirect_to(namespace_project_settings_ci_cd_path) end end + + context 'when default_git_depth is not specified' do + let(:params) { { ci_cd_settings_attributes: { default_git_depth: 10 } } } + + before do + project.ci_cd_settings.update!(default_git_depth: nil) + end + + it 'set specified git depth' do + subject + + project.reload + expect(project.default_git_depth).to eq(10) + end + end end end end diff --git a/spec/factories/pages_domain_acme_orders.rb b/spec/factories/pages_domain_acme_orders.rb new file mode 100644 index 00000000000..7f9ee1c8f9c --- /dev/null +++ b/spec/factories/pages_domain_acme_orders.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :pages_domain_acme_order do + pages_domain + url { 'https://example.com/' } + expires_at { 1.day.from_now } + challenge_token { 'challenge_token' } + challenge_file_content { 'filecontent' } + + private_key { OpenSSL::PKey::RSA.new(4096).to_pem } + + trait :expired do + expires_at { 1.day.ago } + end + end +end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 83cd686818c..f6c498f7a4c 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Admin Appearance' do diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index c4dbe23f6b4..93ccb03d822 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -302,22 +302,22 @@ describe 'Admin updates settings' do group = create(:group) page.within('.as-performance-bar') do - check 'Enable the Performance Bar' + check 'Enable access to the Performance Bar' fill_in 'Allowed group', with: group.path click_on 'Save changes' end expect(page).to have_content "Application settings saved successfully" - expect(find_field('Enable the Performance Bar')).to be_checked + expect(find_field('Enable access to the Performance Bar')).to be_checked expect(find_field('Allowed group').value).to eq group.path page.within('.as-performance-bar') do - uncheck 'Enable the Performance Bar' + uncheck 'Enable access to the Performance Bar' click_on 'Save changes' end expect(page).to have_content 'Application settings saved successfully' - expect(find_field('Enable the Performance Bar')).not_to be_checked + expect(find_field('Enable access to the Performance Bar')).not_to be_checked expect(find_field('Allowed group').value).to be_nil end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index dfa1c92ea49..d523e2992db 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe "Dashboard Issues Feed" do diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index e1bc4eca619..59230d6891a 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Group merge requests page' do diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb index d8be554d734..713cd944f8c 100644 --- a/spec/features/instance_statistics/conversational_development_index_spec.rb +++ b/spec/features/instance_statistics/conversational_development_index_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Conversational Development Index' do diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index bc0ec58bd24..5bdd9113b06 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Issues' do @@ -208,7 +210,7 @@ describe 'Issues' do let(:issue) { @issue } it 'allows filtering by issues with no specified assignee' do - visit project_issues_path(project, assignee_id: IssuableFinder::NONE) + visit project_issues_path(project, assignee_id: IssuableFinder::FILTER_NONE) expect(page).to have_content 'foobar' expect(page).not_to have_content 'barbaz' diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb index 6539e6e9208..da15a4bda4b 100644 --- a/spec/features/merge_request/user_merges_merge_request_spec.rb +++ b/spec/features/merge_request/user_merges_merge_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe "User merges a merge request", :js do diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index bd91fae1453..2dee0e26954 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -33,7 +33,7 @@ describe 'Merge requests > User lists merge requests' do end it 'filters on no assignee' do - visit_merge_requests(project, assignee_id: IssuableFinder::NONE) + visit_merge_requests(project, assignee_id: IssuableFinder::FILTER_NONE) expect(current_path).to eq(project_merge_requests_path(project)) expect(page).to have_content 'merge-test' diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb index 76abc640077..95685a3c7ff 100644 --- a/spec/features/project_variables_spec.rb +++ b/spec/features/project_variables_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Project variables', :js do diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index a85e7333ba8..ce382c19fc1 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Clusters', :js do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 25b3ac00604..1de153db41c 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Pipeline', :js do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 27f6ed56283..b5112758475 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Project' do diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index a198e65046f..044a47567be 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe "Profile access" do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 89fdaceaa9f..bf38d083ca6 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -241,14 +241,6 @@ describe IssuesFinder do end end - context 'filtering by legacy No+Label' do - let(:params) { { label_name: Label::NONE } } - - it 'returns issues with no labels' do - expect(issues).to contain_exactly(issue1, issue3, issue4) - end - end - context 'filtering by any label' do let(:params) { { label_name: described_class::FILTER_ANY } } diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js index 3257a3fb8a3..4dd27e94d97 100644 --- a/spec/frontend/boards/modal_store_spec.js +++ b/spec/frontend/boards/modal_store_spec.js @@ -1,7 +1,7 @@ /* global ListIssue */ -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import Store from '~/boards/stores/modal_store'; diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 66b22fa2681..6de06a9e2d5 100644 --- a/spec/frontend/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -1,5 +1,10 @@ import Clusters from '~/clusters/clusters_bundle'; -import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants'; +import { + APPLICATION_STATUS, + INGRESS_DOMAIN_SUFFIX, + APPLICATIONS, + RUNNER, +} from '~/clusters/constants'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import { loadHTMLFixture } from 'helpers/fixtures'; @@ -353,4 +358,30 @@ describe('Clusters', () => { }); }); }); + + describe('updateApplication', () => { + const params = { version: '1.0.0' }; + let storeUpdateApplication; + let installApplication; + + beforeEach(() => { + storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication'); + installApplication = jest.spyOn(cluster.service, 'installApplication'); + + cluster.updateApplication({ id: RUNNER, params }); + }); + + afterEach(() => { + storeUpdateApplication.mockRestore(); + installApplication.mockRestore(); + }); + + it('calls store updateApplication method', () => { + expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER); + }); + + it('sends installApplication request', () => { + expect(installApplication).toHaveBeenCalledWith(RUNNER, params); + }); + }); }); diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index 7c781b72355..9f127ccb690 100644 --- a/spec/frontend/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -245,26 +245,26 @@ describe('Application Row', () => { }); }); - describe('Upgrade button', () => { + describe('Update button', () => { it('has indeterminate state on page load', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: null, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).toBe(null); + expect(updateBtn).toBe(null); }); - it('has enabled "Upgrade" when "upgradeAvailable" is true', () => { + it('has enabled "Update" when "updateAvailable" is true', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, - upgradeAvailable: true, + updateAvailable: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(upgradeBtn.innerHTML).toContain('Upgrade'); + expect(updateBtn).not.toBe(null); + expect(updateBtn.innerHTML).toContain('Update'); }); it('has enabled "Retry update" when update process fails', () => { @@ -273,10 +273,10 @@ describe('Application Row', () => { status: APPLICATION_STATUS.INSTALLED, updateFailed: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(upgradeBtn.innerHTML).toContain('Retry update'); + expect(updateBtn).not.toBe(null); + expect(updateBtn.innerHTML).toContain('Retry update'); }); it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => { @@ -284,53 +284,51 @@ describe('Application Row', () => { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - expect(upgradeBtn).not.toBe(null); - expect(vm.isUpgrading).toBe(true); - expect(upgradeBtn.innerHTML).toContain('Updating'); + expect(updateBtn).not.toBe(null); + expect(vm.isUpdating).toBe(true); + expect(updateBtn.innerHTML).toContain('Updating'); }); - it('clicking upgrade button emits event', () => { + it('clicking update button emits event', () => { jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLED, - upgradeAvailable: true, + updateAvailable: true, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - upgradeBtn.click(); + updateBtn.click(); - expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', { + expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', { id: DEFAULT_APPLICATION_STATE.id, params: {}, }); }); - it('clicking disabled upgrade button emits nothing', () => { + it('clicking disabled update button emits nothing', () => { jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, }); - const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button'); + const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button'); - upgradeBtn.click(); + updateBtn.click(); expect(eventHub.$emit).not.toHaveBeenCalled(); }); - it('displays an error message if application upgrade failed', () => { + it('displays an error message if application update failed', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', status: APPLICATION_STATUS.INSTALLED, updateFailed: true, }); - const failureMessage = vm.$el.querySelector( - '.js-cluster-application-upgrade-failure-message', - ); + const failureMessage = vm.$el.querySelector('.js-cluster-application-update-details'); expect(failureMessage).not.toBe(null); expect(failureMessage.innerHTML).toContain( @@ -338,7 +336,7 @@ describe('Application Row', () => { ); }); - it('displays a success toast message if application upgrade was successful', () => { + it('displays a success toast message if application update was successful', () => { vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, title: 'GitLab Runner', @@ -349,13 +347,13 @@ describe('Application Row', () => { vm.updateSuccessful = true; return vm.$nextTick(() => { - expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.'); + expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner updated successfully.'); }); }); }); describe('Version', () => { - it('displays a version number if application has been upgraded', () => { + it('displays a version number if application has been updated', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -363,15 +361,15 @@ describe('Application Row', () => { updateSuccessful: true, version, }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); - expect(upgradeDetails.innerHTML).toContain('Upgraded'); + expect(updateDetails.innerHTML).toContain('Updated'); expect(versionEl).not.toBe(null); expect(versionEl.innerHTML).toContain(version); }); - it('contains a link to the chart repo if application has been upgraded', () => { + it('contains a link to the chart repo if application has been updated', () => { const version = '0.1.45'; const chartRepo = 'https://gitlab.com/charts/gitlab-runner'; vm = mountComponent(ApplicationRow, { @@ -381,13 +379,13 @@ describe('Application Row', () => { chartRepo, version, }); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); expect(versionEl.href).toEqual(chartRepo); expect(versionEl.target).toEqual('_blank'); }); - it('does not display a version number if application upgrade failed', () => { + it('does not display a version number if application update failed', () => { const version = '0.1.45'; vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, @@ -395,10 +393,10 @@ describe('Application Row', () => { updateFailed: true, version, }); - const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details'); - const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version'); + const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details'); + const versionEl = vm.$el.querySelector('.js-cluster-application-update-version'); - expect(upgradeDetails.innerHTML).toContain('failed'); + expect(updateDetails.innerHTML).toContain('failed'); expect(versionEl).toBe(null); }); }); diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js index e057e2ac955..c146ef79be7 100644 --- a/spec/frontend/clusters/services/application_state_machine_spec.js +++ b/spec/frontend/clusters/services/application_state_machine_spec.js @@ -127,7 +127,7 @@ describe('applicationStateMachine', () => { describe(`current state is ${UPDATING}`, () => { it.each` expectedState | event | effects - ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true, updateAcknowledged: false }} + ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true }} ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index 0d129349799..f2cc413512d 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -85,11 +85,10 @@ describe('Clusters Store', () => { statusReason: mockResponseData.applications[2].status_reason, requestReason: null, version: mockResponseData.applications[2].version, - upgradeAvailable: mockResponseData.applications[2].update_available, + updateAvailable: mockResponseData.applications[2].update_available, chartRepo: 'https://gitlab.com/charts/gitlab-runner', installed: false, installFailed: false, - updateAcknowledged: true, updateFailed: false, updateSuccessful: false, uninstallable: false, diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js index e74598ae20a..702ef0be5aa 100644 --- a/spec/frontend/helpers/timeout.js +++ b/spec/frontend/helpers/timeout.js @@ -5,7 +5,13 @@ const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk'); let testTimeoutNS; export const setTestTimeout = newTimeoutMS => { - testTimeoutNS = newTimeoutMS * NS_PER_MS; + const newTimeoutNS = newTimeoutMS * NS_PER_MS; + // never accept a smaller timeout than the default + if (newTimeoutNS < testTimeoutNS) { + return; + } + + testTimeoutNS = newTimeoutNS; jest.setTimeout(newTimeoutMS); }; @@ -13,7 +19,13 @@ export const setTestTimeout = newTimeoutMS => { // Useful for tests with jQuery, which is very slow in big DOMs. let temporaryTimeoutNS = null; export const setTestTimeoutOnce = newTimeoutMS => { - temporaryTimeoutNS = newTimeoutMS * NS_PER_MS; + const newTimeoutNS = newTimeoutMS * NS_PER_MS; + // never accept a smaller timeout than the default + if (newTimeoutNS < testTimeoutNS) { + return; + } + + temporaryTimeoutNS = newTimeoutNS; }; export const initializeTestTimeout = defaultTimeoutMS => { diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index 818404bad81..77d7478d317 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -5,6 +5,7 @@ import { bytesToGiB, numberToHumanSize, sum, + isOdd, } from '~/lib/utils/number_utils'; describe('Number Utils', () => { @@ -98,4 +99,14 @@ describe('Number Utils', () => { expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15); }); }); + + describe('isOdd', () => { + it('should return 0 with a even number', () => { + expect(isOdd(2)).toEqual(0); + }); + + it('should return 1 with a odd number', () => { + expect(isOdd(1)).toEqual(1); + }); + }); }); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index eca240c9c18..c771984a137 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -1,5 +1,12 @@ import * as urlUtils from '~/lib/utils/url_utility'; +const setWindowLocation = value => { + Object.defineProperty(window, 'location', { + writable: true, + value, + }); +}; + describe('URL utility', () => { describe('webIDEUrl', () => { afterEach(() => { @@ -110,12 +117,9 @@ describe('URL utility', () => { describe('getBaseURL', () => { beforeEach(() => { - global.window = Object.create(window); - Object.defineProperty(window, 'location', { - value: { - host: 'gitlab.com', - protocol: 'https:', - }, + setWindowLocation({ + protocol: 'https:', + host: 'gitlab.com', }); }); @@ -191,4 +195,32 @@ describe('URL utility', () => { }); }); }); + + describe('getWebSocketProtocol', () => { + it.each` + protocol | expectation + ${'http:'} | ${'ws:'} + ${'https:'} | ${'wss:'} + `('returns "$expectation" with "$protocol" protocol', ({ protocol, expectation }) => { + setWindowLocation({ + protocol, + host: 'example.com', + }); + + expect(urlUtils.getWebSocketProtocol()).toEqual(expectation); + }); + }); + + describe('getWebSocketUrl', () => { + it('joins location host to path', () => { + setWindowLocation({ + protocol: 'http:', + host: 'example.com', + }); + + const path = '/lorem/ipsum?a=bc'; + + expect(urlUtils.getWebSocketUrl(path)).toEqual('ws://example.com/lorem/ipsum?a=bc'); + }); + }); }); diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js index 3b609484b9e..d4a3073374a 100644 --- a/spec/frontend/reports/components/report_section_spec.js +++ b/spec/frontend/reports/components/report_section_spec.js @@ -197,4 +197,44 @@ describe('Report section', () => { expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); }); }); + + describe('Success and Error slots', () => { + const createComponent = status => { + vm = mountComponentWithSlots(ReportSection, { + props: { + status, + hasIssues: true, + }, + slots: { + success: ['This is a success'], + loading: ['This is loading'], + error: ['This is an error'], + }, + }); + }; + + it('only renders success slot when status is "SUCCESS"', () => { + createComponent('SUCCESS'); + + expect(vm.$el.textContent.trim()).toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders error slot when status is "ERROR"', () => { + createComponent('ERROR'); + + expect(vm.$el.textContent.trim()).toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders loading slot when status is "LOADING"', () => { + createComponent('LOADING'); + + expect(vm.$el.textContent.trim()).toContain('This is loading'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + }); + }); }); diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap index 1b4564303e4..86bfde1a28c 100644 --- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -22,6 +22,8 @@ exports[`Repository table row component renders table row 1`] = ` </a> <!----> + + <!----> </td> <td diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index a70dc7bb866..90a502966ad 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -1,4 +1,5 @@ import { shallowMount, RouterLinkStub } from '@vue/test-utils'; +import { GlBadge } from '@gitlab/ui'; import TableRow from '~/repository/components/table/row.vue'; let vm; @@ -98,4 +99,16 @@ describe('Repository table row component', () => { expect(vm.find('a').attributes('href')).toEqual('https://test.com'); }); + + it('renders LFS badge', () => { + factory({ + id: '1', + path: 'test', + type: 'commit', + currentPath: '/', + lfsOid: '1', + }); + + expect(vm.find(GlBadge).exists()).toBe(true); + }); }); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index c24f0bc4776..7e7cc1488b8 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -15,7 +15,7 @@ afterEach(() => }), ); -initializeTestTimeout(500); +initializeTestTimeout(process.env.CI ? 5000 : 500); // fail tests for unmocked requests beforeEach(done => { diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js index 4a8de5fc4f1..63880b85625 100644 --- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js @@ -15,31 +15,37 @@ function formatWarning(string) { describe('Issue Warning Component', () => { describe('isLocked', () => { it('should render locked issue warning information', () => { - const vm = mountComponent(IssueWarning, { + const props = { isLocked: true, - }); + lockedIssueDocsPath: 'docs/issues/locked', + }; + const vm = mountComponent(IssueWarning, props); expect( vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), ).toMatch(/lock$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This issue is locked. Only project members can comment.', + 'This issue is locked. Only project members can comment. Learn more', ); + expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath); }); }); describe('isConfidential', () => { it('should render confidential issue warning information', () => { - const vm = mountComponent(IssueWarning, { + const props = { isConfidential: true, - }); + confidentialIssueDocsPath: '/docs/issues/confidential', + }; + const vm = mountComponent(IssueWarning, props); expect( vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'), ).toMatch(/eye-slash$/); expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual( - 'This is a confidential issue. Your comment will not be visible to the public.', + 'This is a confidential issue. People without permission will never get a notification. Learn more', ); + expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath); }); }); diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index 9982288e206..c162fdbbb47 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do end end - it 'increases complexity based on arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1) + context 'when field is a connection' do + it 'increases complexity based on arguments' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 - end + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 + end - it 'does not increase complexity when filtering by iids' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + it 'does not increase complexity when filtering by iids' do + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 + expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + end end end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index bffcdbfe915..798fe00de97 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do end it 'increases field complexity based on arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4 expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index 395e08081d3..20e197e9f73 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -57,7 +57,7 @@ describe Resolvers::NamespaceProjectsResolver, :nested_groups do end it 'has an high complexity regardless of arguments' do - field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100) + field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100) expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24 expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index a7fb156d9a8..0d3c3e37daf 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -28,18 +28,29 @@ describe Types::BaseField do expect(field.to_graphql.complexity).to eq 12 end - it 'sets complexity depending on arguments for resolvers' do - field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + context 'when field has a resolver proc' do + context 'and is a connection' do + let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) } - expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 - expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 - end + it 'sets complexity depending on arguments for resolvers' do + expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 + expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 + end - it 'sets complexity depending on number load limits for resolvers' do - field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + it 'sets complexity depending on number load limits for resolvers' do + expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 + expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + end + end - expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 - expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + context 'and is not a connection' do + it 'sets complexity as normal' do + field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true) + + expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2 + expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2 + end + end end end end diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb index b12e214ca84..22c11aff90a 100644 --- a/spec/graphql/types/tree/blob_type_spec.rb +++ b/spec/graphql/types/tree/blob_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' describe Types::Tree::BlobType do it { expect(described_class.graphql_name).to eq('Blob') } - it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) } + it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :lfs_oid) } end diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index e1017130bed..13b708a03d5 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import eventHub from '~/boards/eventhub'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/list'; import boardsStore from '~/boards/stores/boards_store'; import boardCard from '~/boards/components/board_card.vue'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index b5559db8784..e81115e10c9 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -6,8 +6,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index a5bf97bdcc2..8a20911cc66 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -4,8 +4,8 @@ import Vue from 'vue'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import IssueCardInner from '~/boards/components/issue_card_inner.vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index e4ff3eb381f..bb7abe52eae 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,8 +1,8 @@ /* global ListIssue */ import Vue from 'vue'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index bb6fc6c693d..15c9ff6dfb4 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -4,8 +4,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import _ from 'underscore'; -import '~/vue_shared/models/label'; -import '~/vue_shared/models/assignee'; +import '~/boards/models/label'; +import '~/boards/models/assignee'; import '~/boards/models/issue'; import '~/boards/models/list'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 2839922fbd3..e6a969bd855 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => { saveButton, errorBox, saveEndpoint: container.dataset.saveEndpoint, + maskableRegex: container.dataset.maskableRegex, }); spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough(); @@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => { expect(row.dataset.isPersisted).toEqual('true'); }); }); + + describe('maskableRegex', () => { + it('takes in the regex provided by the data attribute', () => { + expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex); + }); + }); }); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 394e60fc22c..064113e879a 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -150,6 +150,65 @@ describe('VariableList', () => { .then(done) .catch(done.fail); }); + + describe('validateMaskability', () => { + let $row; + + const maskingErrorElement = '.js-row:last-child .masking-validation-error'; + + beforeEach(() => { + $row = $wrapper.find('.js-row:last-child'); + $row.find('.ci-variable-masked-item .js-project-feature-toggle').click(); + }); + + it('has a regex provided via a data attribute', () => { + expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + }); + + it('allows values that are 8 characters long', done => { + $row.find('.js-ci-variable-input-value').val('looooong'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toHaveClass('hide'); + }) + .then(done) + .catch(done.fail); + }); + + it('rejects values that are shorter than 8 characters', done => { + $row.find('.js-ci-variable-input-value').val('short'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toBeVisible(); + }) + .then(done) + .catch(done.fail); + }); + + it('allows values with base 64 characters', done => { + $row.find('.js-ci-variable-input-value').val('abcABC123_+=/-'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toHaveClass('hide'); + }) + .then(done) + .catch(done.fail); + }); + + it('rejects values with other special characters', done => { + $row.find('.js-ci-variable-input-value').val('1234567$'); + + getSetTimeoutPromise() + .then(() => { + expect($wrapper.find(maskingErrorElement)).toBeVisible(); + }) + .then(done) + .catch(done.fail); + }); + }); }); describe('toggleEnableRow method', () => { diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index cfe0c4bad71..dc3fb16eb40 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -18,7 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-expander.js-toggle-button'); const getShaElement = vm => vm.$el.querySelector('.commit-sha-group'); const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link'); -const getCommitterElement = vm => vm.$el.querySelector('.commiter'); +const getCommitterElement = vm => vm.$el.querySelector('.committer'); const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions'); describe('diffs/components/commit_item', () => { diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index e6fb08bcc49..dd2313dc800 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -719,4 +719,20 @@ describe('IDE store file actions', () => { .catch(done.fail); }); }); + + describe('triggerFilesChange', () => { + beforeEach(() => { + spyOn(eventHub, '$emit'); + }); + + it('emits event that files have changed', done => { + store + .dispatch('triggerFilesChange') + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change'); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 04e236fb042..37354283cab 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -488,7 +488,7 @@ describe('Multi-file store actions', () => { 'path', store.state, [{ type: types.DELETE_ENTRY, payload: 'path' }], - [{ type: 'burstUnusedSeal' }], + [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }], done, ); }); @@ -510,7 +510,7 @@ describe('Multi-file store actions', () => { payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, }, ], - [{ type: 'deleteEntry', payload: 'test' }], + [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }], done, ); }); @@ -558,6 +558,7 @@ describe('Multi-file store actions', () => { }, }, { type: 'deleteEntry', payload: 'test' }, + { type: 'triggerFilesChange' }, ], done, ); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 80b9b740b94..1a371c3adaf 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -68,7 +68,7 @@ describe('Dashboard', () => { it('shows a getting started empty state when no metrics are present', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, showTimeWindowDropdown: false }, + propsData: { ...propsData }, store, }); @@ -85,7 +85,7 @@ describe('Dashboard', () => { it('shows up a loading state', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -102,7 +102,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showLegend: false, - showTimeWindowDropdown: false, }, store, }); @@ -122,7 +121,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -142,7 +140,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -173,7 +170,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -203,7 +199,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -237,7 +232,6 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, environmentsEndpoint: '', - showTimeWindowDropdown: false, }, store, }); @@ -250,27 +244,6 @@ describe('Dashboard', () => { }); }); - it('does not show the time window dropdown when the feature flag is not set', done => { - component = new DashboardComponent({ - el: document.querySelector('.prometheus-graphs'), - propsData: { - ...propsData, - hasMetrics: true, - showPanels: false, - showTimeWindowDropdown: false, - }, - store, - }); - - setTimeout(() => { - const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); - - expect(timeWindowDropdown).toBeNull(); - - done(); - }); - }); - it('renders the time window dropdown with a set of options', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -278,7 +251,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: true, }, store, }); @@ -304,7 +276,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: true, }, store, }); @@ -338,7 +309,7 @@ describe('Dashboard', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -359,7 +330,7 @@ describe('Dashboard', () => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true }, + propsData: { ...propsData, hasMetrics: true }, store, }); @@ -388,7 +359,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, }, store, }); @@ -424,7 +394,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, externalDashboardUrl: '/mockUrl', }, store, @@ -450,7 +419,6 @@ describe('Dashboard', () => { ...propsData, hasMetrics: true, showPanels: false, - showTimeWindowDropdown: false, externalDashboardUrl: '', }, store, diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js index 2159e4ddf16..1f2c07385a7 100644 --- a/spec/javascripts/notes/components/note_actions_spec.js +++ b/spec/javascripts/notes/components/note_actions_spec.js @@ -66,7 +66,7 @@ describe('noteActions', () => { expect(wrapper.find('.js-note-edit').exists()).toBe(true); }); - it('should be possible to report abuse to GitLab', () => { + it('should be possible to report abuse to admin', () => { expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true); }); diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 6e215ea1561..c788da55cd2 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -2,8 +2,12 @@ require 'spec_helper' describe API::Helpers::Pagination do let(:resource) { Project.all } - let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" } - let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" } + let(:custom_port) { 8080 } + let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" } + + before do + stub_config_setting(port: custom_port) + end subject do Class.new.include(described_class).new @@ -48,7 +52,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -71,7 +75,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -171,7 +175,7 @@ describe API::Helpers::Pagination do it 'returns the right link to the next page' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") + expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') end @@ -224,9 +228,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="prev"') end @@ -290,8 +294,8 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="last"') expect(val).not_to include('rel="prev"') end @@ -318,9 +322,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '1') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) expect(val).not_to include('rel="next"') end @@ -367,8 +371,8 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) - expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) expect(val).not_to include('rel="prev"') expect(val).not_to include('rel="next"') expect(val).not_to include('page=0') diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 43222ddb5e2..7c94cf37e32 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -155,6 +155,13 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do it_behaves_like "external issue tracker" end + + context "with a lowercase prefix" do + let(:issue) { ExternalIssue.new("gl-030", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end end context "jira project" do diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb index 91b0499375d..7119c826bca 100644 --- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb @@ -117,4 +117,27 @@ describe Banzai::Pipeline::GfmPipeline do expect(output).not_to include("javascript") end end + + describe 'emoji in references' do + set(:project) { create(:project, :public) } + let(:emoji) { '💯' } + + it 'renders a label reference with emoji inside' do + create(:label, project: project, name: emoji) + + output = described_class.to_html("#{Label.reference_prefix}\"#{emoji}\"", project: project) + + expect(output).to include(emoji) + expect(output).to include(Gitlab::Routing.url_helpers.project_issues_path(project, label_name: emoji)) + end + + it 'renders a milestone reference with emoji inside' do + milestone = create(:milestone, project: project, title: emoji) + + output = described_class.to_html("#{Milestone.reference_prefix}\"#{emoji}\"", project: project) + + expect(output).to include(emoji) + expect(output).to include(Gitlab::Routing.url_helpers.milestone_path(milestone)) + end + end end diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb index 27281333348..0a5b99d27e7 100644 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb @@ -3,6 +3,12 @@ require 'spec_helper' # rubocop:disable RSpec/FactoriesInMigrationSpecs describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do describe '#perform' do + before do + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + end + context 'when diff files can be deleted' do let(:merge_request) { create(:merge_request, :merged) } let!(:merge_request_diff) do diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb index 3e009fed0f1..c6bc3db88a3 100644 --- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb @@ -9,6 +9,9 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration before do # This migration was created before we introduced metadata configs stub_feature_flags(ci_build_metadata_config: false) + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) end let!(:internal_pipeline) { create(:ci_pipeline, source: :web) } diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb new file mode 100644 index 00000000000..180520b27e7 --- /dev/null +++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Cluster::PumaWorkerKillerObserver do + let(:counter) { Gitlab::Metrics::NullMetric.instance } + + before do + allow(Gitlab::Metrics).to receive(:counter) + .with(any_args) + .and_return(counter) + end + + describe '#callback' do + subject { described_class.new } + + it 'increments timeout counter' do + worker = double(index: 0) + + expect(counter) + .to receive(:increment) + .with({ worker: 'worker_0' }) + + subject.callback.call(worker) + end + end +end diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index 753c74ff814..6a6cf1429c8 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -5,39 +5,66 @@ require 'fast_spec_helper' require 'gitlab/danger/teammate' describe Gitlab::Danger::Teammate do - subject { described_class.new({ 'projects' => projects }) } + subject { described_class.new(options) } + let(:options) { { 'projects' => projects, 'role' => role } } let(:projects) { { project => capabilities } } + let(:role) { 'Engineer, Manage' } + let(:labels) { [] } let(:project) { double } - describe 'multiple roles project project' do - let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] } + context 'when having multiple capabilities' do + let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] } it '#reviewer? supports multiple roles per project' do - expect(subject.reviewer?(project, :backend)).to be_truthy + expect(subject.reviewer?(project, :backend, labels)).to be_truthy end it '#traintainer? supports multiple roles per project' do - expect(subject.traintainer?(project, :database)).to be_truthy + expect(subject.traintainer?(project, :qa, labels)).to be_truthy end it '#maintainer? supports multiple roles per project' do - expect(subject.maintainer?(project, :frontend)).to be_truthy + expect(subject.maintainer?(project, :frontend, labels)).to be_truthy + end + + context 'when labels contain Create and the category is test' do + let(:labels) { ['Create'] } + + context 'when role is Test Automation Engineer, Create' do + let(:role) { 'Test Automation Engineer, Create' } + + it '#reviewer? returns true' do + expect(subject.reviewer?(project, :test, labels)).to be_truthy + end + + it '#maintainer? returns false' do + expect(subject.maintainer?(project, :test, labels)).to be_falsey + end + end + + context 'when role is Test Automation Engineer, Manage' do + let(:role) { 'Test Automation Engineer, Manage' } + + it '#reviewer? returns false' do + expect(subject.reviewer?(project, :test, labels)).to be_falsey + end + end end end - describe 'one role project project' do + context 'when having single capability' do let(:capabilities) { 'reviewer backend' } it '#reviewer? supports one role per project' do - expect(subject.reviewer?(project, :backend)).to be_truthy + expect(subject.reviewer?(project, :backend, labels)).to be_truthy end it '#traintainer? supports one role per project' do - expect(subject.traintainer?(project, :database)).to be_falsey + expect(subject.traintainer?(project, :database, labels)).to be_falsey end it '#maintainer? supports one role per project' do - expect(subject.maintainer?(project, :frontend)).to be_falsey + expect(subject.maintainer?(project, :frontend, labels)).to be_falsey end end end diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index b236c1a9c49..ed9a1e23529 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -53,6 +53,8 @@ describe Gitlab::DataBuilder::Note do .to eq(issue.reload.hook_attrs.except('updated_at')) expect(data[:issue]['updated_at']) .to be >= issue.hook_attrs['updated_at'] + expect(data[:issue]['labels']) + .to eq(issue.hook_attrs['labels']) end context 'with confidential issue' do diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb index e4fe01a671f..52630ba0223 100644 --- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::GitalyClient::ConflictsService do end let(:source_branch) { 'master' } let(:target_branch) { 'feature' } - let(:commit_message) { 'Solving conflicts' } + let(:commit_message) { 'Solving conflicts\n\nTést' } let(:resolution) do Gitlab::Git::Conflict::Resolution.new(user, files, commit_message) end @@ -51,6 +51,25 @@ describe Gitlab::GitalyClient::ConflictsService do subject end + context 'with branches with UTF-8 characters' do + let(:source_branch) { 'testòbranch' } + let(:target_branch) { 'ábranch' } + + it 'handles commit messages with UTF-8 characters' do + allow(::Gitlab::GitalyClient).to receive(:call).and_call_original + expect(::Gitlab::GitalyClient).to receive(:call).with(anything, :conflicts_service, :resolve_conflicts, any_args) do |*args| + # Force the generation of request messages by iterating through the enumerator + message = args[3].to_a.first + params = [message.header.commit_message, message.header.source_branch, message.header.target_branch] + expect(params.map(&:encoding).uniq).to eq([Encoding::ASCII_8BIT]) + + double(resolution_error: nil) + end + + subject + end + end + it 'raises a relevant exception if resolution_error is present' do expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:resolve_conflicts) .with(kind_of(Enumerator), kind_of(Hash)).and_return(double(resolution_error: "something happened")) diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index 7579a6577b9..18663a72fcd 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -78,6 +78,24 @@ describe Gitlab::GitalyClient::OperationService do subject end + describe '#user_merge_to_ref' do + let(:branch) { 'my-branch' } + let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } + let(:ref) { 'refs/merge-requests/x/merge' } + let(:message) { 'validación' } + let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') } + + subject { client.user_merge_to_ref(user, source_sha, branch, ref, message) } + + it 'sends a user_merge_to_ref message' do + expect_any_instance_of(Gitaly::OperationService::Stub) + .to receive(:user_merge_to_ref).with(kind_of(Gitaly::UserMergeToRefRequest), kind_of(Hash)) + .and_return(response) + + subject + end + end + context "when pre_receive_error is present" do let(:response) do Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed") diff --git a/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb new file mode 100644 index 00000000000..0137b1029cd --- /dev/null +++ b/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Graphql::Loaders::BatchCommitLoader do + include GraphqlHelpers + + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) } + let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) } + + describe '#find' do + it 'batch-resolves LFS blob IDs' do + expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).once.and_call_original + + result = batch do + [blob, otherblob].map { |b| described_class.new(repository, b.id).find } + end + + expect(result.first).to eq(blob.lfs_oid) + expect(result.last).to eq(nil) + end + end +end diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb index 74622f356de..fcd92586362 100644 --- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb @@ -3,23 +3,11 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Challenge do - delegated_methods = { - url: 'https://example.com/', - status: 'pending', - token: 'tokenvalue', - file_content: 'hereisfilecontent', - request_validation: true - } + include LetsEncryptHelpers - let(:acme_challenge) do - acme_challenge = instance_double('Acme::Client::Resources::Challenge') - allow(acme_challenge).to receive_messages(delegated_methods) - acme_challenge - end - - let(:challenge) { described_class.new(acme_challenge) } + let(:challenge) { described_class.new(acme_challenge_double) } - delegated_methods.each do |method, value| + LetsEncryptHelpers::ACME_CHALLENGE_METHODS.each do |method, value| describe "##{method}" do it 'delegates to Acme::Client::Resources::Challenge' do expect(challenge.public_send(method)).to eq(value) diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb index ee7058baf8d..1a759103c44 100644 --- a/spec/lib/gitlab/lets_encrypt/order_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb @@ -3,20 +3,13 @@ require 'spec_helper' describe ::Gitlab::LetsEncrypt::Order do - delegated_methods = { - url: 'https://example.com/', - status: 'valid' - } - - let(:acme_order) do - acme_order = instance_double('Acme::Client::Resources::Order') - allow(acme_order).to receive_messages(delegated_methods) - acme_order - end + include LetsEncryptHelpers + + let(:acme_order) { acme_order_double } let(:order) { described_class.new(acme_order) } - delegated_methods.each do |method, value| + LetsEncryptHelpers::ACME_ORDER_METHODS.each do |method, value| describe "##{method}" do it 'delegates to Acme::Client::Resources::Order' do expect(order.public_send(method)).to eq(value) @@ -25,15 +18,24 @@ describe ::Gitlab::LetsEncrypt::Order do end describe '#new_challenge' do - before do - challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') - authorization = instance_double('Acme::Client::Resources::Authorization') - allow(authorization).to receive(:http).and_return(challenge) - allow(acme_order).to receive(:authorizations).and_return([authorization]) - end - it 'returns challenge' do expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge) end end + + describe '#request_certificate' do + let(:private_key) do + OpenSSL::PKey::RSA.new(4096).to_pem + end + + it 'generates csr and finalizes order' do + expect(acme_order).to receive(:finalize) do |csr:| + expect do + csr.csr # it's being evaluated lazily + end.not_to raise_error + end + + order.request_certificate(domain: 'example.com', private_key: private_key) + end + end end diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb index f9c0daf1ef1..32296caf819 100644 --- a/spec/lib/gitlab/omniauth_initializer_spec.rb +++ b/spec/lib/gitlab/omniauth_initializer_spec.rb @@ -83,5 +83,13 @@ describe Gitlab::OmniauthInitializer do subject.execute([cas3_config]) end + + it 'configures name for openid_connect' do + openid_connect_config = { 'name' => 'openid_connect', 'args' => {} } + + expect(devise_config).to receive(:omniauth).with(:openid_connect, name: 'openid_connect') + + subject.execute([openid_connect_config]) + end end end diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb index fe46c67a920..5f0a7e925ca 100644 --- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb +++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb @@ -15,6 +15,13 @@ describe Gitlab::Template::GitlabCiYmlTemplate do expect(all).to include('Docker') expect(all).to include('Ruby') end + + it 'ensure that the template name is used exactly once' do + all = subject.all.group_by(&:name) + duplicates = all.select { |_, templates| templates.length > 1 } + + expect(duplicates).to be_empty + end end describe '.find' do diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb index 0d5e33bfd46..a6dd2e08079 100644 --- a/spec/migrations/enqueue_reset_merge_status_spec.rb +++ b/spec/migrations/enqueue_reset_merge_status_spec.rb @@ -40,9 +40,12 @@ describe EnqueueResetMergeStatus, :migration, :sidekiq do .to be_scheduled_delayed_migration(5.minutes, 1, 2) expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(10.minutes, 3, 3) + .to be_scheduled_delayed_migration(10.minutes, 3, 4) - expect(BackgroundMigrationWorker.jobs.size).to eq(2) + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(15.minutes, 5, 5) + + expect(BackgroundMigrationWorker.jobs.size).to eq(3) end end end diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb index afcaefa0591..abf39317188 100644 --- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb +++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb @@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do end end + let(:domains_table) { table(:pages_domains) } + describe '#up' do it 'enqueues a verification worker for every domain' do - domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") } + domains = Array.new(3) do |i| + domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}") + end expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3) diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb index 13b8919343e..e8c44c141c3 100644 --- a/spec/migrations/remove_orphaned_label_links_spec.rb +++ b/spec/migrations/remove_orphaned_label_links_spec.rb @@ -10,6 +10,12 @@ describe RemoveOrphanedLabelLinks, :migration do let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs let(:label) { create_label } + before do + # This migration was created before we introduced ProjectCiCdSetting#default_git_depth + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) + allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) + end + context 'add foreign key on label_id' do let!(:label_link_with_label) { create_label_link(label_id: label.id) } let!(:label_link_without_label) { create_label_link(label_id: nil) } diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb new file mode 100644 index 00000000000..54f3e264df0 --- /dev/null +++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb') + +describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do + let(:migration_class) { described_class::MIGRATION } + let(:migration_name) { migration_class.to_s.demodulize } + + let(:domains_table) { table(:pages_domains) } + + let(:certificate) do + File.read('spec/fixtures/passphrase_x509_certificate.crt') + end + + before do + domains_table.create!(domain: "domain1.example.com", verification_code: "123") + domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '') + domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate) + domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + first_id = domains_table.find_by_domain("domain3.example.com").id + last_id = domains_table.find_by_domain("domain4.example.com").id + + expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id) + expect(BackgroundMigrationWorker.jobs.size).to eq(1) + end + end + end + + it 'sets certificate valid_not_before/not_after' do + perform_enqueued_jobs do + migrate! + + domain = domains_table.find_by_domain("domain3.example.com") + expect(domain.certificate_valid_not_before) + .to eq(Time.parse("2018-03-23 14:02:08 UTC")) + expect(domain.certificate_valid_not_after) + .to eq(Time.parse("2019-03-23 14:02:08 UTC")) + end + end +end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 3ab013ddc0e..4d53e4aad8a 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -88,13 +88,6 @@ describe BroadcastMessage do expect(Rails.cache).not_to receive(:delete).with(described_class::CACHE_KEY) expect(described_class.current.length).to eq(0) end - - it 'clears the legacy cache key' do - create(:broadcast_message, :future) - - expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY) - expect(described_class.current.length).to eq(0) - end end describe '#attributes' do @@ -164,7 +157,6 @@ describe BroadcastMessage do message = create(:broadcast_message) expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY) - expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY) message.flush_redis_cache end diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 53df9e0bc05..7faa196623f 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -232,4 +232,17 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do end end end + + describe 'default options' do + let(:cached_class) { Class.new { include ReactiveCaching } } + + subject { cached_class.new } + + it { expect(subject.reactive_cache_lease_timeout).to be_a(ActiveSupport::Duration) } + it { expect(subject.reactive_cache_refresh_interval).to be_a(ActiveSupport::Duration) } + it { expect(subject.reactive_cache_lifetime).to be_a(ActiveSupport::Duration) } + + it { expect(subject.reactive_cache_key).to respond_to(:call) } + it { expect(subject.reactive_cache_worker_finder).to respond_to(:call) } + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 956c5675f38..fc28c216b21 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1088,22 +1088,6 @@ describe MergeRequest do end end - describe "#reset_auto_merge" do - let(:merge_if_green) do - create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user), - merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" } - end - - it "sets the item to false" do - merge_if_green.reset_auto_merge - merge_if_green.reload - - expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey - expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil - expect(merge_if_green.merge_params["commit_message"]).to be_nil - end - end - describe '#committers' do it 'returns all the committers of every commit in the merge request' do users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email| diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb new file mode 100644 index 00000000000..4ffb4fc7389 --- /dev/null +++ b/spec/models/pages_domain_acme_order_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomainAcmeOrder do + using RSpec::Parameterized::TableSyntax + + describe '.expired' do + let!(:not_expired_order) { create(:pages_domain_acme_order) } + let!(:expired_order) { create(:pages_domain_acme_order, :expired) } + + it 'returns only expired orders' do + expect(described_class.count).to eq(2) + expect(described_class.expired).to eq([expired_order]) + end + end + + describe '.find_by_domain_and_token' do + let!(:domain) { create(:pages_domain, domain: 'test.com') } + let!(:acme_order) { create(:pages_domain_acme_order, challenge_token: 'righttoken', pages_domain: domain) } + + where(:domain_name, :challenge_token, :present) do + 'test.com' | 'righttoken' | true + 'test.com' | 'wrongtoken' | false + 'test.org' | 'righttoken' | false + end + + with_them do + subject { described_class.find_by_domain_and_token(domain_name, challenge_token).present? } + + it { is_expected.to eq(present) } + end + end + + subject { create(:pages_domain_acme_order) } + + describe 'associations' do + it { is_expected.to belong_to(:pages_domain) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:pages_domain) } + it { is_expected.to validate_presence_of(:expires_at) } + it { is_expected.to validate_presence_of(:url) } + it { is_expected.to validate_presence_of(:challenge_token) } + it { is_expected.to validate_presence_of(:challenge_file_content) } + it { is_expected.to validate_presence_of(:private_key) } + end +end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index ec4d4517f82..fdc81359d34 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -81,6 +81,17 @@ describe PagesDomain do end end + describe 'when certificate is specified' do + let(:domain) { build(:pages_domain) } + + it 'saves validity time' do + domain.save + + expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC")) + expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC")) + end + end + describe 'validate certificate' do subject { domain } diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index 4aa62028169..eb3a7e527c9 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -21,4 +21,44 @@ describe ProjectCiCdSetting do 2.times { described_class.available? } end end + + describe 'validations' do + it 'validates default_git_depth is between 0 and 1000 or nil' do + expect(subject).to validate_numericality_of(:default_git_depth) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than_or_equal_to(1000) + .allow_nil + end + end + + describe '#default_git_depth' do + let(:default_value) { described_class::DEFAULT_GIT_DEPTH } + + it 'sets default value for new records' do + project = create(:project) + + expect(project.ci_cd_settings.default_git_depth).to eq(default_value) + end + + it 'does not set default value if present' do + project = build(:project) + project.build_ci_cd_settings(default_git_depth: 0) + project.save! + + expect(project.reload.ci_cd_settings.default_git_depth).to eq(0) + end + + context 'when feature flag :ci_set_project_default_git_depth is disabled' do + let(:project) { create(:project) } + + before do + stub_feature_flags(ci_set_project_default_git_depth: { enabled: false } ) + end + + it 'does not set default value for new records' do + expect(project.ci_cd_settings.default_git_depth).to eq(nil) + end + end + end end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index 358873f9a2f..1cb49d83ffa 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -197,6 +197,18 @@ describe ProjectStatistics do expect(statistics.storage_size).to eq 9 end + + it 'works during wiki_size backfill' do + statistics.update!( + repository_size: 2, + wiki_size: nil, + lfs_objects_size: 3 + ) + + statistics.reload + + expect(statistics.storage_size).to eq 5 + end end describe '.increment_statistic' do diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index 3430111ca9e..620f34bac79 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -119,21 +119,31 @@ describe Ci::BuildRunnerPresenter do end describe '#git_depth' do - subject { presenter.git_depth } - let(:build) { create(:ci_build) } - it 'returns the correct git depth' do - is_expected.to eq(0) - end + subject(:git_depth) { presenter.git_depth } context 'when GIT_DEPTH variable is specified' do before do create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) end - it 'returns the correct git depth' do - is_expected.to eq(1) + it 'returns its value' do + expect(git_depth).to eq(1) + end + end + + it 'defaults to git depth setting for the project' do + expect(git_depth).to eq(build.project.default_git_depth) + end + + context 'when feature flag :ci_project_git_depth is disabled' do + before do + stub_feature_flags(ci_project_git_depth: { enabled: false }) + end + + it 'defaults to 0' do + expect(git_depth).to eq(0) end end end @@ -144,24 +154,24 @@ describe Ci::BuildRunnerPresenter do let(:build) { create(:ci_build) } it 'returns the correct refspecs' do - is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', - '+refs/heads/*:refs/remotes/origin/*') + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") end - context 'when GIT_DEPTH variable is specified' do - before do - create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) - end + context 'when ref is tag' do + let(:build) { create(:ci_build, :tag) } it 'returns the correct refspecs' do - is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}") end - context 'when ref is tag' do - let(:build) { create(:ci_build, :tag) } + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end it 'returns the correct refspecs' do - is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}") + is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', + '+refs/heads/*:refs/remotes/origin/*') end end end @@ -173,17 +183,27 @@ describe Ci::BuildRunnerPresenter do it 'returns the correct refspecs' do is_expected - .to contain_exactly('+refs/heads/*:refs/remotes/origin/*', - '+refs/tags/*:refs/tags/*', - '+refs/merge-requests/1/head:refs/merge-requests/1/head') + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + + context 'when GIT_DEPTH is zero' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline) + end + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head', + '+refs/heads/*:refs/remotes/origin/*', + '+refs/tags/*:refs/tags/*') + end end context 'when pipeline is legacy detached merge request pipeline' do let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } it 'returns the correct refspecs' do - is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', - '+refs/heads/*:refs/remotes/origin/*') + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") end end end diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb index 8290473821c..a4b37905af3 100644 --- a/spec/rack_servers/puma_spec.rb +++ b/spec/rack_servers/puma_spec.rb @@ -20,7 +20,7 @@ describe 'Puma' do File.write(config_path, config_lines) cmd = %W[puma -e test -C #{config_path} #{File.join(__dir__, 'configs/config.ru')}] - @puma_master_pid = spawn(*cmd) + @puma_master_pid = spawn({ 'DISABLE_PUMA_WORKER_KILLER' => '1' }, *cmd) wait_puma_boot!(@puma_master_pid, File.join(project_root, 'tmp/tests/puma-worker-ready')) WebMock.allow_net_connect! end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 9b9cc778fb3..f32ffd1c77b 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -276,14 +276,6 @@ describe API::Issues do it 'returns issues with no assignee' do issue2 = create(:issue, author: user2, project: project) - get api('/issues', user), params: { assignee_id: 0, scope: 'all' } - - expect_paginated_array_response(issue2.id) - end - - it 'returns issues with no assignee' do - issue2 = create(:issue, author: user2, project: project) - get api('/issues', user), params: { assignee_id: 'None', scope: 'all' } expect_paginated_array_response(issue2.id) @@ -496,18 +488,6 @@ describe API::Issues do expect_paginated_array_response(closed_issue.id) end - - it 'returns an array of issues with no label when using the legacy No+Label filter' do - get api('/issues', user), params: { labels: 'No Label' } - - expect_paginated_array_response(closed_issue.id) - end - - it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do - get api('/issues', user), params: { labels: ['No Label'] } - - expect_paginated_array_response(closed_issue.id) - end end it 'returns an empty array if no issue matches milestone' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 3202050ac20..038c958b5cc 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -444,8 +444,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do 'sha' => job.sha, 'before_sha' => job.before_sha, 'ref_type' => 'branch', - 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*], - 'depth' => 0 } + 'refspecs' => ["+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"], + 'depth' => project.default_git_depth } end let(:expected_steps) do @@ -531,7 +531,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end - context 'when GIT_DEPTH is not specified' do + context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do + before do + project.update!(default_git_depth: nil) + end + it 'specifies refspecs' do request_job @@ -587,7 +591,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end - context 'when GIT_DEPTH is not specified' do + context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do + before do + project.update!(default_git_depth: nil) + end + it 'specifies refspecs' do request_job diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb new file mode 100644 index 00000000000..ee2531197b1 --- /dev/null +++ b/spec/requests/api/task_completion_status_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'task completion status response' do + set(:user) { create(:user) } + set(:project) do + create(:project, :public, creator_id: user.id, namespace: user.namespace) + end + + shared_examples 'taskable completion status provider' do |path| + samples = [ + { + description: '', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: 'Lorem ipsum', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: %{- [ ] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 1 + }, + { + description: %{- [ ] task 1 + - [ ] task 2 }, + expected_count: 2, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1 + - [x] task 2 }, + expected_count: 2, + expected_completed_count: 2 + }, + { + description: %{- [ ] task 1}, + expected_count: 1, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1}, + expected_count: 1, + expected_completed_count: 1 + } + ] + samples.each do |sample_data| + context "with a description of #{sample_data[:description].inspect}" do + before do + taskable.update!(description: sample_data[:description]) + + get api("#{path}?iids[]=#{taskable.iid}", user) + end + + it { expect(response).to have_gitlab_http_status(200) } + + it 'returns the expected results' do + expect(json_response).to be_an Array + expect(json_response).not_to be_empty + + task_completion_status = json_response.first['task_completion_status'] + expect(task_completion_status['count']).to eq(sample_data[:expected_count]) + expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count]) + end + end + end + end + + context 'task list completion status for issues' do + it_behaves_like 'taskable completion status provider', '/issues' do + let(:taskable) { create(:issue, project: project, author: user) } + end + end + + context 'task list completion status for merge_requests' do + it_behaves_like 'taskable completion status provider', '/merge_requests' do + let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) } + end + end +end diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb new file mode 100644 index 00000000000..35d60d6abbb --- /dev/null +++ b/spec/services/auto_merge/base_service_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AutoMerge::BaseService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user, params) } + let(:merge_request) { create(:merge_request) } + let(:params) { {} } + + describe '#execute' do + subject { service.execute(merge_request) } + + before do + allow(AutoMergeProcessWorker).to receive(:perform_async) {} + end + + it 'sets properies to the merge request' do + subject + + merge_request.reload + expect(merge_request).to be_auto_merge_enabled + expect(merge_request.merge_user).to eq(user) + expect(merge_request.auto_merge_strategy).to eq('base') + end + + it 'yields block' do + expect { |b| service.execute(merge_request, &b) }.to yield_control.once + end + + it 'returns activated strategy name' do + is_expected.to eq(:base) + end + + context 'when merge parameters are given' do + let(:params) do + { + 'commit_message' => "Merge branch 'patch-12' into 'master'", + 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18", + 'should_remove_source_branch' => false, + 'squash' => false, + 'squash_commit_message' => "Update README.md" + } + end + + it 'sets merge parameters' do + subject + + merge_request.reload + expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'") + expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18') + expect(merge_request.merge_params['should_remove_source_branch']).to eq(false) + expect(merge_request.merge_params['squash']).to eq(false) + expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md') + end + end + + context 'when strategy is merge when pipeline succeeds' do + let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) } + + it 'sets the auto merge strategy' do + subject + + merge_request.reload + expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) + end + + it 'returns activated strategy name' do + is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS.to_sym) + end + + it 'calls AutoMergeProcessWorker' do + expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id).once + + subject + end + end + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end + + it 'does not yield block' do + expect { |b| service.execute(merge_request, &b) }.not_to yield_control + end + + it 'returns failed' do + is_expected.to eq(:failed) + end + end + end + + describe '#cancel' do + subject { service.cancel(merge_request) } + + let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) } + + it 'removes properies from the merge request' do + subject + + merge_request.reload + expect(merge_request).not_to be_auto_merge_enabled + expect(merge_request.merge_user).to be_nil + expect(merge_request.auto_merge_strategy).to be_nil + end + + it 'yields block' do + expect { |b| service.cancel(merge_request, &b) }.to yield_control.once + end + + it 'returns success status' do + expect(subject[:status]).to eq(:success) + end + + context 'when merge params are set' do + before do + merge_request.update!(merge_params: + { + 'should_remove_source_branch' => false, + 'commit_message' => "Merge branch 'patch-12' into 'master'", + 'squash_commit_message' => "Update README.md", + 'auto_merge_strategy' => 'merge_when_pipeline_succeeds' + }) + end + + it 'removes merge parameters' do + subject + + merge_request.reload + expect(merge_request.merge_params['should_remove_source_branch']).to be_nil + expect(merge_request.merge_params['commit_message']).to be_nil + expect(merge_request.merge_params['squash_commit_message']).to be_nil + expect(merge_request.merge_params['auto_merge_strategy']).to be_nil + end + end + + context 'when failed to save' do + before do + allow(merge_request).to receive(:save) { false } + end + + it 'does not yield block' do + expect { |b| service.execute(merge_request, &b) }.not_to yield_control + end + + it 'returns error status' do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq("Can't cancel the automatic merge") + end + end + end +end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index fbfcd95e204..f566d235787 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -406,6 +406,18 @@ describe MergeRequests::UpdateService, :mailer do expect(pending_todo.reload).to be_done end end + + context 'when auto merge is enabled and target branch changed' do + before do + AutoMergeService.new(project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) + + update_merge_request({ target_branch: 'target' }) + end + + it 'marks pending todos as done' do + expect(pending_todo.reload).to be_done + end + end end context 'when the merge request is relabeled' do diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb new file mode 100644 index 00000000000..d59aa9b979e --- /dev/null +++ b/spec/services/pages_domains/create_acme_order_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomains::CreateAcmeOrderService do + include LetsEncryptHelpers + + let(:pages_domain) { create(:pages_domain) } + + let(:challenge) { ::Gitlab::LetsEncrypt::Challenge.new(acme_challenge_double) } + + let(:order_double) do + Gitlab::LetsEncrypt::Order.new(acme_order_double).tap do |order| + allow(order).to receive(:new_challenge).and_return(challenge) + end + end + + let(:lets_encrypt_client) do + instance_double('Gitlab::LetsEncrypt::Client').tap do |client| + allow(client).to receive(:new_order).with(pages_domain.domain) + .and_return(order_double) + end + end + + let(:service) { described_class.new(pages_domain) } + + before do + allow(::Gitlab::LetsEncrypt::Client).to receive(:new).and_return(lets_encrypt_client) + end + + it 'saves order to database before requesting validation' do + allow(pages_domain.acme_orders).to receive(:create!).and_call_original + allow(challenge).to receive(:request_validation).and_call_original + + service.execute + + expect(pages_domain.acme_orders).to have_received(:create!).ordered + expect(challenge).to have_received(:request_validation).ordered + end + + it 'generates and saves private key' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error + end + + it 'properly saves order attributes' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect(saved_order.url).to eq(order_double.url) + expect(saved_order.expires_at).to be_like_time(order_double.expires) + end + + it 'properly saves challenge attributes' do + service.execute + + saved_order = PagesDomainAcmeOrder.last + expect(saved_order.challenge_token).to eq(challenge.token) + expect(saved_order.challenge_file_content).to eq(challenge.file_content) + end +end diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb new file mode 100644 index 00000000000..6d7be27939c --- /dev/null +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomains::ObtainLetsEncryptCertificateService do + include LetsEncryptHelpers + + let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) } + let(:service) { described_class.new(pages_domain) } + + before do + stub_lets_encrypt_settings + end + + def expect_to_create_acme_challenge + expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain) + .and_wrap_original do |m, *args| + create_service = m.call(*args) + + expect(create_service).to receive(:execute) + + create_service + end + end + + def stub_lets_encrypt_order(url, status) + order = ::Gitlab::LetsEncrypt::Order.new(acme_order_double(status: status)) + + allow_any_instance_of(::Gitlab::LetsEncrypt::Client).to( + receive(:load_order).with(url).and_return(order) + ) + + order + end + + context 'when there is no acme order' do + it 'creates acme order' do + expect_to_create_acme_challenge + + service.execute + end + end + + context 'when there is expired acme order' do + let!(:existing_order) do + create(:pages_domain_acme_order, :expired, pages_domain: pages_domain) + end + + it 'removes acme order and creates new one' do + expect_to_create_acme_challenge + + service.execute + + expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil + end + end + + %w(pending processing).each do |status| + context "there is an order in '#{status}' status" do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + before do + stub_lets_encrypt_order(existing_order.url, status) + end + + it 'does not raise errors' do + expect do + service.execute + end.not_to raise_error + end + end + end + + context 'when order is ready' do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + let!(:api_order) do + stub_lets_encrypt_order(existing_order.url, 'ready') + end + + it 'request certificate' do + expect(api_order).to receive(:request_certificate).and_call_original + + service.execute + end + end + + context 'when order is valid' do + let(:existing_order) do + create(:pages_domain_acme_order, pages_domain: pages_domain) + end + + let!(:api_order) do + stub_lets_encrypt_order(existing_order.url, 'valid') + end + + let(:certificate) do + key = OpenSSL::PKey.read(existing_order.private_key) + + subject = "/C=BE/O=Test/OU=Test/CN=#{pages_domain.domain}" + + cert = OpenSSL::X509::Certificate.new + cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject) + cert.not_before = Time.now + cert.not_after = 1.year.from_now + cert.public_key = key.public_key + cert.serial = 0x0 + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = cert + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension("basicConstraints", "CA:TRUE", true), + ef.create_extension("subjectKeyIdentifier", "hash") + ] + cert.add_extension ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + + cert.sign key, OpenSSL::Digest::SHA1.new + + cert.to_pem + end + + before do + expect(api_order).to receive(:certificate) { certificate } + end + + it 'saves private_key and certificate for domain' do + service.execute + + expect(pages_domain.key).to be_present + expect(pages_domain.certificate).to eq(certificate) + end + + it 'removes order from database' do + service.execute + + expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil + end + end +end diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index f7261cd7125..d25e9958831 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -56,7 +56,9 @@ describe PreviewMarkdownService do expect(Gitlab::Diff::SuggestionsParser) .to receive(:parse) - .with(text, position: position, project: merge_request.project) + .with(text, position: position, + project: merge_request.project, + supports_suggestion: true) .and_call_original result = service.execute diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index ec3f1782e8f..3211a6e1310 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -145,6 +145,30 @@ describe Projects::ForkService do end end + context "CI/CD settings" do + let(:to_project) { fork_project(@from_project, @to_user) } + + context "when origin has git depth specified" do + before do + @from_project.update(default_git_depth: 42) + end + + it "inherits default_git_depth from the origin project" do + expect(to_project.default_git_depth).to eq(42) + end + end + + context "when origin does not define git depth" do + before do + @from_project.update!(default_git_depth: nil) + end + + it "the fork has git depth set to 0" do + expect(to_project.default_git_depth).to eq(0) + end + end + end + context "when project has restricted visibility level" do context "and only one visibility level is restricted" do before do diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index ccd44e615a8..d95f9e3349b 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -96,7 +96,7 @@ describe Suggestions::CreateService do it 'creates no suggestion when diff file is not found' do expect_next_instance_of(DiffNote) do |diff_note| - expect(diff_note).to receive(:latest_diff_file).twice { nil } + expect(diff_note).to receive(:latest_diff_file).once { nil } end expect { subject.execute }.not_to change(Suggestion, :count) diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb index 89dfbf931d2..5d5a0a7b5d2 100644 --- a/spec/support/features/reportable_note_shared_examples.rb +++ b/spec/support/features/reportable_note_shared_examples.rb @@ -20,7 +20,7 @@ shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - expect(dropdown).to have_link('Report abuse to GitLab', href: abuse_report_path) + expect(dropdown).to have_link('Report abuse to admin', href: abuse_report_path) if type == 'issue' || type == 'merge_request' expect(dropdown).to have_button('Delete comment') @@ -33,7 +33,7 @@ shared_examples 'reportable note' do |type| dropdown = comment.find(more_actions_selector) open_dropdown(dropdown) - dropdown.click_link('Report abuse to GitLab') + dropdown.click_link('Report abuse to admin') expect(find('#user_name')['value']).to match(note.author.username) expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note)) diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb index 7f0886b451c..2857416ad95 100644 --- a/spec/support/helpers/lets_encrypt_helpers.rb +++ b/spec/support/helpers/lets_encrypt_helpers.rb @@ -1,6 +1,26 @@ # frozen_string_literal: true module LetsEncryptHelpers + ACME_ORDER_METHODS = { + url: 'https://example.com/', + status: 'valid', + expires: 2.days.from_now + }.freeze + + ACME_CHALLENGE_METHODS = { + status: 'pending', + token: 'tokenvalue', + file_content: 'hereisfilecontent', + request_validation: true + }.freeze + + def stub_lets_encrypt_settings + stub_application_setting( + lets_encrypt_notification_email: 'myemail@test.example.com', + lets_encrypt_terms_of_service_accepted: true + ) + end + def stub_lets_encrypt_client client = instance_double('Acme::Client') @@ -16,4 +36,24 @@ module LetsEncryptHelpers client end + + def acme_challenge_double + challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01') + allow(challenge).to receive_messages(ACME_CHALLENGE_METHODS) + challenge + end + + def acme_authorization_double + authorization = instance_double('Acme::Client::Resources::Authorization') + allow(authorization).to receive(:http).and_return(acme_challenge_double) + authorization + end + + def acme_order_double(attributes = {}) + acme_order = instance_double('Acme::Client::Resources::Order') + allow(acme_order).to receive_messages(ACME_ORDER_METHODS.merge(attributes)) + allow(acme_order).to receive(:authorizations).and_return([acme_authorization_double]) + allow(acme_order).to receive(:finalize) + acme_order + end end diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 54d9f5b15f2..54d9f5b15f2 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb index 782a2d97746..a931c4df99f 100644 --- a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -20,12 +20,6 @@ shared_examples 'no assignee filter' do end it 'returns issuables not assigned to any assignee' do - params[:assignee_id] = 0 - - expect(issuables).to contain_exactly(*expected_issuables) - end - - it 'returns issuables not assigned to any assignee' do params[:assignee_id] = 'none' expect(issuables).to contain_exactly(*expected_issuables) diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb index 8a9ab02eaca..ae47f364296 100644 --- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb +++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb @@ -12,10 +12,10 @@ describe 'projects/notes/_more_actions_dropdown' do assign(:project, project) end - it 'shows Report abuse to GitLab button if not editable and not current users comment' do + it 'shows Report abuse to admin button if not editable and not current users comment' do render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note - expect(rendered).to have_link('Report abuse to GitLab') + expect(rendered).to have_link('Report abuse to admin') end it 'does not show the More actions button if not editable and current users comment' do @@ -24,10 +24,10 @@ describe 'projects/notes/_more_actions_dropdown' do expect(rendered).not_to have_selector('.dropdown.more-actions') end - it 'shows Report abuse to GitLab and Delete buttons if editable and not current users comment' do + it 'shows Report abuse to admin and Delete buttons if editable and not current users comment' do render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note - expect(rendered).to have_link('Report abuse to GitLab') + expect(rendered).to have_link('Report abuse to admin') expect(rendered).to have_link('Delete comment') end |