diff options
Diffstat (limited to 'spec')
37 files changed, 739 insertions, 172 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/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/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/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..5ee9425c491 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 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/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/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/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/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/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/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/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/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/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb new file mode 100644 index 00000000000..197fa16961d --- /dev/null +++ b/spec/services/auto_merge/base_service_spec.rb @@ -0,0 +1,144 @@ +# 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) } + + 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 + 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/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/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 |