diff options
Diffstat (limited to 'spec')
51 files changed, 1099 insertions, 211 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/factories/uploads.rb b/spec/factories/uploads.rb index 426abdc2a6c..52f6962f16b 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -54,10 +54,7 @@ FactoryBot.define do end trait :attachment_upload do - transient do - mount_point :attachment - end - + mount_point :attachment model { build(:note) } uploader "AttachmentUploader" 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/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/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/gitlab/background_migration/migrate_legacy_uploads_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb new file mode 100644 index 00000000000..802c8fb8c97 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateLegacyUploads, :migration, schema: 20190103140724 do + let(:test_dir) { FileUploader.options['storage_path'] } + + # rubocop: disable RSpec/FactoriesInMigrationSpecs + let!(:namespace) { create(:namespace) } + let!(:project) { create(:project, :legacy_storage, namespace: namespace) } + let!(:issue) { create(:issue, project: project) } + + let!(:note1) { create(:note, note: 'some note text awesome', project: project, noteable: issue) } + let!(:note2) { create(:note, note: 'some note', project: project, noteable: issue) } + + let!(:hashed_project) { create(:project, namespace: namespace) } + let!(:issue_hashed_project) { create(:issue, project: hashed_project) } + let!(:note_hashed_project) { create(:note, note: 'some note', project: hashed_project, attachment: 'text.pdf', noteable: issue_hashed_project) } + + let!(:standard_upload) do + create(:upload, + path: "secretabcde/image.png", + model_id: create(:project).id, model_type: 'Project', uploader: 'FileUploader') + end + + def create_remote_upload(model, filename) + create(:upload, :attachment_upload, + path: "note/attachment/#{model.id}/#{filename}", secret: nil, + store: ObjectStorage::Store::REMOTE, model: model) + end + + def create_upload(model, filename, with_file = true) + params = { + path: "uploads/-/system/note/attachment/#{model.id}/#{filename}", + model: model, + store: ObjectStorage::Store::LOCAL + } + + upload = if with_file + create(:upload, :with_file, :attachment_upload, params) + else + create(:upload, :attachment_upload, params) + end + + model.update(attachment: upload.build_uploader) + model.attachment.upload + end + + let(:start_id) { 1 } + let(:end_id) { 10000 } + + def new_upload_legacy + Upload.find_by(model_id: project.id, model_type: 'Project') + end + + def new_upload_hashed + Upload.find_by(model_id: hashed_project.id, model_type: 'Project') + end + + shared_examples 'migrates files correctly' do + before do + described_class.new.perform(start_id, end_id) + end + + it 'removes all the legacy upload records' do + expect(Upload.where(uploader: 'AttachmentUploader')).to be_empty + + expect(standard_upload.reload).to eq(standard_upload) + end + + it 'creates new upload records correctly' do + expect(new_upload_legacy.secret).not_to be_nil + expect(new_upload_legacy.path).to end_with("#{new_upload_legacy.secret}/image.png") + expect(new_upload_legacy.model_id).to eq(project.id) + expect(new_upload_legacy.model_type).to eq('Project') + expect(new_upload_legacy.uploader).to eq('FileUploader') + + expect(new_upload_hashed.secret).not_to be_nil + expect(new_upload_hashed.path).to end_with("#{new_upload_hashed.secret}/text.pdf") + expect(new_upload_hashed.model_id).to eq(hashed_project.id) + expect(new_upload_hashed.model_type).to eq('Project') + expect(new_upload_hashed.uploader).to eq('FileUploader') + end + + it 'updates the legacy upload notes so that they include the file references in the markdown' do + expected_path = File.join('/uploads', new_upload_legacy.secret, 'image.png') + expected_markdown = "some note text awesome \n " + expect(note1.reload.note).to eq(expected_markdown) + + expected_path = File.join('/uploads', new_upload_hashed.secret, 'text.pdf') + expected_markdown = "some note \n [text.pdf](#{expected_path})" + expect(note_hashed_project.reload.note).to eq(expected_markdown) + end + + it 'removed the attachments from the note model' do + expect(note1.reload.attachment.file).to be_nil + expect(note2.reload.attachment.file).to be_nil + expect(note_hashed_project.reload.attachment.file).to be_nil + end + end + + context 'when legacy uploads are stored in local storage' do + let!(:legacy_upload1) { create_upload(note1, 'image.png') } + let!(:legacy_upload_not_found) { create_upload(note2, 'image.png', false) } + let!(:legacy_upload_hashed) { create_upload(note_hashed_project, 'text.pdf', with_file: true) } + + shared_examples 'removes legacy local files' do + it 'removes all the legacy upload records' do + expect(File.exist?(legacy_upload1.absolute_path)).to be_truthy + expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_truthy + + described_class.new.perform(start_id, end_id) + + expect(File.exist?(legacy_upload1.absolute_path)).to be_falsey + expect(File.exist?(legacy_upload_hashed.absolute_path)).to be_falsey + end + end + + context 'when object storage is disabled for FileUploader' do + it_behaves_like 'migrates files correctly' + it_behaves_like 'removes legacy local files' + + it 'moves legacy uploads to the correct location' do + described_class.new.perform(start_id, end_id) + + expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png') + expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf') + + expect(File.exist?(expected_path1)).to be_truthy + expect(File.exist?(expected_path2)).to be_truthy + end + + context 'when the upload move fails' do + it 'does not remove old uploads' do + expect(FileUploader).to receive(:copy_to).twice.and_raise('failed') + + described_class.new.perform(start_id, end_id) + + expect(legacy_upload1.reload).to eq(legacy_upload1) + expect(legacy_upload_hashed.reload).to eq(legacy_upload_hashed) + expect(standard_upload.reload).to eq(standard_upload) + end + end + end + + context 'when object storage is enabled for FileUploader' do + before do + stub_uploads_object_storage(FileUploader) + end + + it_behaves_like 'migrates files correctly' + it_behaves_like 'removes legacy local files' + + # The process of migrating to object storage is a manual one, + # so it would go against expectations to automatically migrate these files + # to object storage during this migration. + # After this migration, these files should be able to successfully migrate to object storage. + it 'stores files locally' do + described_class.new.perform(start_id, end_id) + + expected_path1 = File.join(test_dir, 'uploads', namespace.path, project.path, new_upload_legacy.secret, 'image.png') + expected_path2 = File.join(test_dir, 'uploads', hashed_project.disk_path, new_upload_hashed.secret, 'text.pdf') + + expect(File.exist?(expected_path1)).to be_truthy + expect(File.exist?(expected_path2)).to be_truthy + end + end + + context 'with legacy_diff_note upload' do + let!(:merge_request) { create(:merge_request, source_project: project) } + let!(:legacy_diff_note) { create(:legacy_diff_note_on_merge_request, note: 'some note', project: project, noteable: merge_request) } + let!(:legacy_upload_diff_note) do + create(:upload, :with_file, :attachment_upload, + path: "uploads/-/system/note/attachment/#{legacy_diff_note.id}/some_legacy.pdf", model: legacy_diff_note) + end + + before do + described_class.new.perform(start_id, end_id) + end + + it 'does not remove legacy diff note file' do + expect(File.exist?(legacy_upload_diff_note.absolute_path)).to be_truthy + end + + it 'removes all the legacy upload records except for the one with legacy_diff_note' do + expect(Upload.where(uploader: 'AttachmentUploader')).to eq([legacy_upload_diff_note]) + end + + it 'adds link to the troubleshooting documentation to the note' do + help_doc_link = 'https://docs.gitlab.com/ee/administration/troubleshooting/migrations.html#legacy-upload-migration' + + expect(legacy_diff_note.reload.note).to include(help_doc_link) + end + end + end + + context 'when legacy uploads are stored in object storage' do + let!(:legacy_upload1) { create_remote_upload(note1, 'image.png') } + let!(:legacy_upload_not_found) { create_remote_upload(note2, 'non-existing.pdf') } + let!(:legacy_upload_hashed) { create_remote_upload(note_hashed_project, 'text.pdf') } + let(:remote_files) do + [ + { key: "#{legacy_upload1.path}" }, + { key: "#{legacy_upload_hashed.path}" } + ] + end + let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) } + let(:bucket) { connection.directories.create(key: 'uploads') } + + def create_remote_files + remote_files.each { |file| bucket.files.create(file) } + end + + before do + stub_uploads_object_storage(FileUploader) + create_remote_files + end + + it_behaves_like 'migrates files correctly' + + it 'moves legacy uploads to the correct remote location' do + described_class.new.perform(start_id, end_id) + + connection = ::Fog::Storage.new(FileUploader.object_store_credentials) + expect(connection.get_object('uploads', new_upload_legacy.path)[:status]).to eq(200) + expect(connection.get_object('uploads', new_upload_hashed.path)[:status]).to eq(200) + end + + it 'removes all the legacy upload records' do + described_class.new.perform(start_id, end_id) + + remote_files.each do |remote_file| + expect(bucket.files.get(remote_file[:key])).to be_nil + end + end + end + # rubocop: enable RSpec/FactoriesInMigrationSpecs +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/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_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/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/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/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/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/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 |