summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/acme_challenges_controller_spec.rb44
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb14
-rw-r--r--spec/factories/pages_domain_acme_orders.rb17
-rw-r--r--spec/factories/uploads.rb5
-rw-r--r--spec/features/admin/admin_appearance_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/instance_statistics/conversational_development_index_spec.rb2
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_merge_request_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb2
-rw-r--r--spec/features/projects/clusters_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js33
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js76
-rw-r--r--spec/frontend/clusters/services/application_state_machine_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js3
-rw-r--r--spec/frontend/helpers/timeout.js16
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js11
-rw-r--r--spec/frontend/test_setup.js2
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js18
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb22
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/types/base_field_spec.rb29
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js16
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js5
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js40
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js2
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb34
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_uploads_spec.rb237
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb47
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb18
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb38
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb8
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb7
-rw-r--r--spec/migrations/enqueue_verify_pages_domain_workers_spec.rb6
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb46
-rw-r--r--spec/models/merge_request_spec.rb16
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb49
-rw-r--r--spec/models/pages_domain_spec.rb11
-rw-r--r--spec/services/auto_merge/base_service_spec.rb144
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb63
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb146
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb4
-rw-r--r--spec/support/helpers/lets_encrypt_helpers.rb40
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb (renamed from spec/support/shared_context/policies/project_policy_shared_context.rb)0
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb8
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 ![image](#{expected_path})"
+ 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