Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-06 15:10:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-06 15:10:29 +0300
commit5564275a0b378298dc6281599cbfe71a937109ff (patch)
treea468e1e60046356410219c35c23a8a428c5e2c5e /spec
parentd87918510a866a5fcbbc2f899ad65c6938ebf5f5 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb1
-rw-r--r--spec/factories/deploy_tokens.rb9
-rw-r--r--spec/factories/group_deploy_tokens.rb8
-rw-r--r--spec/factories/users.rb4
-rw-r--r--spec/features/groups/navbar_spec.rb99
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb2
-rw-r--r--spec/features/profiles/user_edit_preferences_spec.rb27
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb4
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb1
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap39
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js64
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js58
-rw-r--r--spec/frontend/code_navigation/store/actions_spec.js221
-rw-r--r--spec/frontend/code_navigation/store/mutations_spec.js63
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js58
-rw-r--r--spec/initializers/mail_encoding_patch_spec.rb207
-rw-r--r--spec/javascripts/reports/components/modal_spec.js4
-rw-r--r--spec/javascripts/user_popovers_spec.js7
-rw-r--r--spec/lib/gitlab/auth_spec.rb18
-rw-r--r--spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb17
-rw-r--r--spec/lib/gitlab/tab_width_spec.rb31
-rw-r--r--spec/models/deploy_token_spec.rb170
-rw-r--r--spec/models/group_deploy_token_spec.rb17
-rw-r--r--spec/models/user_preference_spec.rb15
-rw-r--r--spec/models/user_spec.rb40
-rw-r--r--spec/policies/project_policy_spec.rb14
30 files changed, 1142 insertions, 64 deletions
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 77e7b32af25..98a9c3eaec6 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -47,6 +47,7 @@ describe Profiles::PreferencesController do
theme_id: '2',
first_day_of_week: '1',
preferred_language: 'jp',
+ tab_width: '5',
render_whitespace_in_code: 'true'
}.with_indifferent_access
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
index 42ed66ac191..e86d4ab8812 100644
--- a/spec/factories/deploy_tokens.rb
+++ b/spec/factories/deploy_tokens.rb
@@ -9,6 +9,7 @@ FactoryBot.define do
read_registry { true }
revoked { false }
expires_at { 5.days.from_now }
+ deploy_token_type { DeployToken.deploy_token_types[:project_type] }
trait :revoked do
revoked { true }
@@ -21,5 +22,13 @@ FactoryBot.define do
trait :expired do
expires_at { Date.today - 1.month }
end
+
+ trait :group do
+ deploy_token_type { DeployToken.deploy_token_types[:group_type] }
+ end
+
+ trait :project do
+ deploy_token_type { DeployToken.deploy_token_types[:project_type] }
+ end
end
end
diff --git a/spec/factories/group_deploy_tokens.rb b/spec/factories/group_deploy_tokens.rb
new file mode 100644
index 00000000000..9ec7d0701be
--- /dev/null
+++ b/spec/factories/group_deploy_tokens.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :group_deploy_token do
+ group
+ deploy_token
+ end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index f83c137b758..34f6da682b6 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -23,6 +23,10 @@ FactoryBot.define do
after(:build) { |user, _| user.block! }
end
+ trait :bot do
+ bot_type { User.bot_types[:alert_bot] }
+ end
+
trait :external do
external { true }
end
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index 5662465d431..8c16dcec42f 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -3,53 +3,53 @@
require 'spec_helper'
describe 'Group navbar' do
- it_behaves_like 'verified navigation bar' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ let(:analytics_nav_item) do
+ {
+ nav_item: _('Analytics'),
+ nav_sub_items: [
+ _('Contribution Analytics')
+ ]
+ }
+ end
- let(:analytics_nav_item) do
+ let(:structure) do
+ [
+ {
+ nav_item: _('Group overview'),
+ nav_sub_items: [
+ _('Details'),
+ _('Activity')
+ ]
+ },
{
- nav_item: _('Analytics'),
+ nav_item: _('Issues'),
nav_sub_items: [
- _('Contribution Analytics')
+ _('List'),
+ _('Board'),
+ _('Labels'),
+ _('Milestones')
]
+ },
+ {
+ nav_item: _('Merge Requests'),
+ nav_sub_items: []
+ },
+ {
+ nav_item: _('Kubernetes'),
+ nav_sub_items: []
+ },
+ (analytics_nav_item if Gitlab.ee?),
+ {
+ nav_item: _('Members'),
+ nav_sub_items: []
}
- end
-
- let(:structure) do
- [
- {
- nav_item: _('Group overview'),
- nav_sub_items: [
- _('Details'),
- _('Activity')
- ]
- },
- {
- nav_item: _('Issues'),
- nav_sub_items: [
- _('List'),
- _('Board'),
- _('Labels'),
- _('Milestones')
- ]
- },
- {
- nav_item: _('Merge Requests'),
- nav_sub_items: []
- },
- {
- nav_item: _('Kubernetes'),
- nav_sub_items: []
- },
- (analytics_nav_item if Gitlab.ee?),
- {
- nav_item: _('Members'),
- nav_sub_items: []
- }
- ]
- end
+ ]
+ end
+ it_behaves_like 'verified navigation bar' do
before do
group.add_maintainer(user)
sign_in(user)
@@ -57,4 +57,21 @@ describe 'Group navbar' do
visit group_path(group)
end
end
+
+ if Gitlab.ee?
+ context 'when productivity analytics is available' do
+ before do
+ stub_licensed_features(productivity_analytics: true)
+
+ analytics_nav_item[:nav_sub_items] << _('Productivity Analytics')
+
+ group.add_maintainer(user)
+ sign_in(user)
+
+ visit group_path(group)
+ end
+
+ it_behaves_like 'verified navigation bar'
+ end
+ end
end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 4f2c5fc73d8..17ff494a6fa 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -20,7 +20,7 @@ describe 'a maintainer edits files on a source-branch of an MR from a fork', :js
end
before do
- stub_feature_flags(web_ide_default: false, single_mr_diff_view: false)
+ stub_feature_flags(web_ide_default: false, single_mr_diff_view: false, code_navigation: false)
target_project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/profiles/user_edit_preferences_spec.rb b/spec/features/profiles/user_edit_preferences_spec.rb
index 2d2da222998..6e61536d5ff 100644
--- a/spec/features/profiles/user_edit_preferences_spec.rb
+++ b/spec/features/profiles/user_edit_preferences_spec.rb
@@ -29,4 +29,31 @@ describe 'User edit preferences profile' do
expect(field).not_to be_checked
end
+
+ describe 'User changes tab width to acceptable value' do
+ it 'shows success message' do
+ fill_in 'Tab width', with: 9
+ click_button 'Save changes'
+
+ expect(page).to have_content('Preferences saved.')
+ end
+
+ it 'saves the value' do
+ tab_width_field = page.find_field('Tab width')
+
+ expect do
+ tab_width_field.fill_in with: 6
+ click_button 'Save changes'
+ end.to change { tab_width_field.value }
+ end
+ end
+
+ describe 'User changes tab width to unacceptable value' do
+ it 'shows error message' do
+ fill_in 'Tab width', with: -1
+ click_button 'Save changes'
+
+ expect(page).to have_content('Failed to save preferences')
+ end
+ end
end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 5d86e4125df..e714d0f7cad 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -13,6 +13,10 @@ describe 'File blob', :js do
wait_for_requests
end
+ before do
+ stub_feature_flags(code_navigation: false)
+ end
+
context 'Ruby file' do
before do
visit_blob('files/ruby/popen.rb')
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index a1d6a8896c7..5d62b2f87bb 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -69,6 +69,8 @@ describe 'Editing file blob', :js do
context 'from blob file path' do
before do
+ stub_feature_flags(code_navigation: false)
+
visit project_blob_path(project, tree_join(branch, file_path))
end
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index b90129d6176..30878b7fb64 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -8,6 +8,7 @@ describe 'User creates blob in new project', :js do
shared_examples 'creating a file' do
before do
+ stub_feature_flags(code_navigation: false)
sign_in(user)
visit project_path(project)
end
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index eb9a4d8cb09..2d4f22e299e 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -14,7 +14,7 @@ describe 'Projects > Files > User creates files', :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(web_ide_default: false)
+ stub_feature_flags(web_ide_default: false, code_navigation: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 0f543e47631..5e36407d9cb 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -14,6 +14,8 @@ describe 'Projects > Files > User deletes files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(code_navigation: false)
+
sign_in(user)
end
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index 4c54bbdcd67..e1eefdcc40f 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -16,6 +16,8 @@ describe 'Projects > Files > User replaces files', :js do
let(:user) { create(:user) }
before do
+ stub_feature_flags(code_navigation: false)
+
sign_in(user)
end
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
new file mode 100644
index 00000000000..dda6d68018e
--- /dev/null
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Code navigation popover component renders popover 1`] = `
+<div
+ class="popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show"
+ style="left: 0px; top: 0px;"
+>
+ <div
+ class="arrow"
+ style="left: 0px;"
+ />
+
+ <div
+ class="border-bottom"
+ >
+ <pre
+ class="border-0 bg-transparent m-0 code highlight"
+ >
+ console.log
+ </pre>
+ </div>
+
+ <div
+ class="popover-body"
+ >
+ <gl-button-stub
+ class="w-100"
+ href="http://test.com"
+ size="md"
+ target="_blank"
+ variant="default"
+ >
+
+ Go to definition
+
+ </gl-button-stub>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
new file mode 100644
index 00000000000..cfdc0dcc6cc
--- /dev/null
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -0,0 +1,64 @@
+import Vuex from 'vuex';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import createState from '~/code_navigation/store/state';
+import App from '~/code_navigation/components/app.vue';
+import Popover from '~/code_navigation/components/popover.vue';
+
+const localVue = createLocalVue();
+const fetchData = jest.fn();
+const showDefinition = jest.fn();
+let wrapper;
+
+localVue.use(Vuex);
+
+function factory(initialState = {}) {
+ const store = new Vuex.Store({
+ state: {
+ ...createState(),
+ ...initialState,
+ },
+ actions: {
+ fetchData,
+ showDefinition,
+ },
+ });
+
+ wrapper = shallowMount(App, { store, localVue });
+}
+
+describe('Code navigation app component', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('fetches data on mount', () => {
+ factory();
+
+ expect(fetchData).toHaveBeenCalled();
+ });
+
+ it('hides popover when no definition set', () => {
+ factory();
+
+ expect(wrapper.find(Popover).exists()).toBe(false);
+ });
+
+ it('renders popover when definition set', () => {
+ factory({
+ currentDefinition: { hover: 'console' },
+ currentDefinitionPosition: { x: 0 },
+ });
+
+ expect(wrapper.find(Popover).exists()).toBe(true);
+ });
+
+ it('calls showDefinition when clicking blob viewer', () => {
+ setFixtures('<div class="blob-viewer"></div>');
+
+ factory();
+
+ document.querySelector('.blob-viewer').click();
+
+ expect(showDefinition).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
new file mode 100644
index 00000000000..ad05504a224
--- /dev/null
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -0,0 +1,58 @@
+import { shallowMount } from '@vue/test-utils';
+import Popover from '~/code_navigation/components/popover.vue';
+
+const MOCK_CODE_DATA = Object.freeze({
+ hover: [
+ {
+ language: 'javascript',
+ value: 'console.log',
+ },
+ ],
+ definition_url: 'http://test.com',
+});
+
+const MOCK_DOCS_DATA = Object.freeze({
+ hover: [
+ {
+ language: null,
+ value: 'console.log',
+ },
+ ],
+ definition_url: 'http://test.com',
+});
+
+let wrapper;
+
+function factory(position, data) {
+ wrapper = shallowMount(Popover, { propsData: { position, data } });
+}
+
+describe('Code navigation popover component', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders popover', () => {
+ factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('code output', () => {
+ it('renders code output', () => {
+ factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA);
+
+ expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(true);
+ expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(false);
+ });
+ });
+
+ describe('documentation output', () => {
+ it('renders code output', () => {
+ factory({ x: 0, y: 0, height: 0 }, MOCK_DOCS_DATA);
+
+ expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(false);
+ expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js
new file mode 100644
index 00000000000..5e29a76f804
--- /dev/null
+++ b/spec/frontend/code_navigation/store/actions_spec.js
@@ -0,0 +1,221 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import actions from '~/code_navigation/store/actions';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { setCurrentHoverElement, addInteractionClass } from '~/code_navigation/utils';
+
+jest.mock('~/flash');
+jest.mock('~/code_navigation/utils');
+
+describe('Code navigation actions', () => {
+ describe('setInitialData', () => {
+ it('commits SET_INITIAL_DATA', done => {
+ testAction(
+ actions.setInitialData,
+ { projectPath: 'test' },
+ {},
+ [{ type: 'SET_INITIAL_DATA', payload: { projectPath: 'test' } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestDataError', () => {
+ it('commits REQUEST_DATA_ERROR', () =>
+ testAction(actions.requestDataError, null, {}, [{ type: 'REQUEST_DATA_ERROR' }], []));
+
+ it('creates a flash message', () =>
+ testAction(actions.requestDataError, null, {}, [{ type: 'REQUEST_DATA_ERROR' }], []).then(
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ },
+ ));
+ });
+
+ describe('fetchData', () => {
+ let mock;
+ const state = {
+ projectPath: 'gitlab-org/gitlab',
+ commitId: '123',
+ blobPath: 'index',
+ };
+ const apiUrl = '/api/1/projects/gitlab-org%2Fgitlab/commits/123/lsif/info';
+
+ beforeEach(() => {
+ window.gon = { api_version: '1' };
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ mock.onGet(apiUrl).replyOnce(200, [
+ {
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ },
+ {
+ start_line: 1,
+ start_char: 0,
+ hover: null,
+ },
+ ]);
+ });
+
+ it('commits REQUEST_DATA_SUCCESS with normalized data', done => {
+ testAction(
+ actions.fetchData,
+ null,
+ state,
+ [
+ { type: 'REQUEST_DATA' },
+ {
+ type: 'REQUEST_DATA_SUCCESS',
+ payload: { '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } } },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('calls addInteractionClass with data', done => {
+ testAction(
+ actions.fetchData,
+ null,
+ state,
+ [
+ { type: 'REQUEST_DATA' },
+ {
+ type: 'REQUEST_DATA_SUCCESS',
+ payload: { '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } } },
+ },
+ ],
+ [],
+ )
+ .then(() => {
+ expect(addInteractionClass).toHaveBeenCalledWith({
+ start_line: 0,
+ start_char: 0,
+ hover: { value: '123' },
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(apiUrl).replyOnce(500);
+ });
+
+ it('dispatches requestDataError', done => {
+ testAction(
+ actions.fetchData,
+ null,
+ state,
+ [{ type: 'REQUEST_DATA' }],
+ [{ type: 'requestDataError' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('showDefinition', () => {
+ let target;
+
+ beforeEach(() => {
+ target = document.createElement('div');
+ });
+
+ it('returns early when no data exists', done => {
+ testAction(actions.showDefinition, { target }, {}, [], [], done);
+ });
+
+ it('commits SET_CURRENT_DEFINITION when target is not code navitation element', done => {
+ testAction(
+ actions.showDefinition,
+ { target },
+ { data: {} },
+ [
+ {
+ type: 'SET_CURRENT_DEFINITION',
+ payload: { definition: undefined, position: undefined },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('commits SET_CURRENT_DEFINITION with LSIF data', done => {
+ target.classList.add('js-code-navigation');
+ target.setAttribute('data-line-index', '0');
+ target.setAttribute('data-char-index', '0');
+
+ testAction(
+ actions.showDefinition,
+ { target },
+ { data: { '0:0': { hover: 'test' } } },
+ [
+ {
+ type: 'SET_CURRENT_DEFINITION',
+ payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('adds hll class to target element', () => {
+ target.classList.add('js-code-navigation');
+ target.setAttribute('data-line-index', '0');
+ target.setAttribute('data-char-index', '0');
+
+ return testAction(
+ actions.showDefinition,
+ { target },
+ { data: { '0:0': { hover: 'test' } } },
+ [
+ {
+ type: 'SET_CURRENT_DEFINITION',
+ payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } },
+ },
+ ],
+ [],
+ ).then(() => {
+ expect(target.classList).toContain('hll');
+ });
+ });
+
+ it('caches current target element', () => {
+ target.classList.add('js-code-navigation');
+ target.setAttribute('data-line-index', '0');
+ target.setAttribute('data-char-index', '0');
+
+ return testAction(
+ actions.showDefinition,
+ { target },
+ { data: { '0:0': { hover: 'test' } } },
+ [
+ {
+ type: 'SET_CURRENT_DEFINITION',
+ payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } },
+ },
+ ],
+ [],
+ ).then(() => {
+ expect(setCurrentHoverElement).toHaveBeenCalledWith(target);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/code_navigation/store/mutations_spec.js b/spec/frontend/code_navigation/store/mutations_spec.js
new file mode 100644
index 00000000000..117a2ed2f14
--- /dev/null
+++ b/spec/frontend/code_navigation/store/mutations_spec.js
@@ -0,0 +1,63 @@
+import mutations from '~/code_navigation/store/mutations';
+import createState from '~/code_navigation/store/state';
+
+let state;
+
+describe('Code navigation mutations', () => {
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('SET_INITIAL_DATA', () => {
+ it('sets initial data', () => {
+ mutations.SET_INITIAL_DATA(state, {
+ projectPath: 'test',
+ commitId: '123',
+ blobPath: 'index.js',
+ });
+
+ expect(state.projectPath).toBe('test');
+ expect(state.commitId).toBe('123');
+ expect(state.blobPath).toBe('index.js');
+ });
+ });
+
+ describe('REQUEST_DATA', () => {
+ it('sets loading true', () => {
+ mutations.REQUEST_DATA(state);
+
+ expect(state.loading).toBe(true);
+ });
+ });
+
+ describe('REQUEST_DATA_SUCCESS', () => {
+ it('sets loading false', () => {
+ mutations.REQUEST_DATA_SUCCESS(state, ['test']);
+
+ expect(state.loading).toBe(false);
+ });
+
+ it('sets data', () => {
+ mutations.REQUEST_DATA_SUCCESS(state, ['test']);
+
+ expect(state.data).toEqual(['test']);
+ });
+ });
+
+ describe('REQUEST_DATA_ERROR', () => {
+ it('sets loading false', () => {
+ mutations.REQUEST_DATA_ERROR(state);
+
+ expect(state.loading).toBe(false);
+ });
+ });
+
+ describe('SET_CURRENT_DEFINITION', () => {
+ it('sets current definition and position', () => {
+ mutations.SET_CURRENT_DEFINITION(state, { definition: 'test', position: { x: 0 } });
+
+ expect(state.currentDefinition).toBe('test');
+ expect(state.currentDefinitionPosition).toEqual({ x: 0 });
+ });
+ });
+});
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
new file mode 100644
index 00000000000..458cc536635
--- /dev/null
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -0,0 +1,58 @@
+import {
+ cachedData,
+ getCurrentHoverElement,
+ setCurrentHoverElement,
+ addInteractionClass,
+} from '~/code_navigation/utils';
+
+afterEach(() => {
+ if (cachedData.has('current')) {
+ cachedData.delete('current');
+ }
+});
+
+describe('getCurrentHoverElement', () => {
+ it.each`
+ value
+ ${'test'}
+ ${undefined}
+ `('it returns cached current key', ({ value }) => {
+ if (value) {
+ cachedData.set('current', value);
+ }
+
+ expect(getCurrentHoverElement()).toEqual(value);
+ });
+});
+
+describe('setCurrentHoverElement', () => {
+ it('sets cached current key', () => {
+ setCurrentHoverElement('test');
+
+ expect(getCurrentHoverElement()).toEqual('test');
+ });
+});
+
+describe('addInteractionClass', () => {
+ beforeEach(() => {
+ setFixtures(
+ '<div id="LC1"><span>console</span><span>.</span><span>log</span></div><div id="LC2"><span>function</span></div>',
+ );
+ });
+
+ it.each`
+ line | char | index
+ ${0} | ${0} | ${0}
+ ${0} | ${8} | ${2}
+ ${1} | ${0} | ${0}
+ `(
+ 'it sets code navigation attributes for line $line and character $char',
+ ({ line, char, index }) => {
+ addInteractionClass({ start_line: line, start_char: char });
+
+ expect(document.querySelectorAll(`#LC${line + 1} span`)[index].classList).toContain(
+ 'js-code-navigation',
+ );
+ },
+ );
+});
diff --git a/spec/initializers/mail_encoding_patch_spec.rb b/spec/initializers/mail_encoding_patch_spec.rb
new file mode 100644
index 00000000000..41074af3503
--- /dev/null
+++ b/spec/initializers/mail_encoding_patch_spec.rb
@@ -0,0 +1,207 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'mail'
+require_relative '../../config/initializers/mail_encoding_patch.rb'
+
+describe 'Mail quoted-printable transfer encoding patch and Unicode characters' do
+ shared_examples 'email encoding' do |email|
+ it 'enclosing in a new object does not change the encoded original' do
+ new_email = Mail.new(email)
+
+ expect(new_email.subject).to eq(email.subject)
+ expect(new_email.from).to eq(email.from)
+ expect(new_email.to).to eq(email.to)
+ expect(new_email.content_type).to eq(email.content_type)
+ expect(new_email.content_transfer_encoding).to eq(email.content_transfer_encoding)
+
+ expect(new_email.encoded).to eq(email.encoded)
+ end
+ end
+
+ context 'with a text email' do
+ context 'with a body that encodes to exactly 74 characters (final newline)' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/plain; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "-123456789-123456789-123456789-123456789-123456789-123456789-123456789-1\n"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+
+ context 'with a body that encodes to exactly 74 characters (no final newline)' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/plain; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "-123456789-123456789-123456789-123456789-123456789-123456789-123456789-12"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+
+ context 'with a body that encodes to exactly 75 characters' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/plain; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "-123456789-123456789-123456789-123456789-123456789-123456789-123456789-12\n"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+ end
+
+ context 'with an html email' do
+ context 'with a body that encodes to exactly 74 characters (final newline)' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/html; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "<p>-123456789-123456789-123456789-123456789-123456789-123456789-1234</p>\n"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+
+ context 'with a body that encodes to exactly 74 characters (no final newline)' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/html; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "<p>-123456789-123456789-123456789-123456789-123456789-123456789-12345</p>"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+
+ context 'with a body that encodes to exactly 75 characters' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/html; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "<p>-123456789-123456789-123456789-123456789-123456789-123456789-12345</p>\n"
+ end
+
+ it_behaves_like 'email encoding', email
+ end
+ end
+
+ context 'a multipart email' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ end
+
+ text_part = Mail::Part.new do
+ content_type 'text/plain; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "\r\n\r\n@john.doe, now known as John Dóe has accepted your invitation to join the Administrator / htmltest project.\r\n\r\nhttp://169.254.169.254:3000/root/htmltest\r\n\r\n-- \r\nYou're receiving this email because of your account on 169.254.169.254.\r\n\r\n\r\n\r\n"
+ end
+
+ html_part = Mail::Part.new do
+ content_type 'text/html; charset=UTF-8'
+ content_transfer_encoding 'quoted-printable'
+ body "\r\n\r\n@john.doe, now known as John Dóe has accepted your invitation to join the Administrator / htmltest project.\r\n\r\nhttp://169.254.169.254:3000/root/htmltest\r\n\r\n-- \r\nYou're receiving this email because of your account on 169.254.169.254.\r\n\r\n\r\n\r\n"
+ end
+
+ email.text_part = text_part
+ email.html_part = html_part
+
+ it_behaves_like 'email encoding', email
+ end
+
+ context 'with non UTF-8 charset' do
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ content_type 'text/plain; charset=windows-1251'
+ content_transfer_encoding 'quoted-printable'
+ body "This line is very long and will be put in multiple quoted-printable lines. Some Russian character: Д\n\n\n".encode('windows-1251')
+ end
+
+ it_behaves_like 'email encoding', email
+
+ it 'can be decoded back' do
+ expect(Mail.new(email).body.decoded.dup.force_encoding('windows-1251').encode('utf-8')).to include('Some Russian character: Д')
+ end
+ end
+
+ context 'with binary content' do
+ context 'can be encoded with \'base64\' content-transfer-encoding' do
+ image = File.binread('spec/fixtures/rails_sample.jpg')
+
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ end
+
+ part = Mail::Part.new
+ part.body = [image].pack('m')
+ part.content_type = 'image/jpg'
+ part.content_transfer_encoding = 'base64'
+
+ email.parts << part
+
+ it_behaves_like 'email encoding', email
+
+ it 'binary contents are not modified' do
+ expect(email.parts.first.decoded).to eq(image)
+
+ # Enclosing in a new Mail object does not corrupt encoded data
+ expect(Mail.new(email).parts.first.decoded).to eq(image)
+ end
+ end
+
+ context 'encoding fails with \'quoted-printable\' content-transfer-encoding' do
+ image = File.binread('spec/fixtures/rails_sample.jpg')
+
+ email = Mail.new do
+ to 'jane.doe@example.com'
+ from 'John Dóe <john.doe@example.com>'
+ subject 'Encoding tést'
+ end
+
+ part = Mail::Part.new
+ part.body = [image].pack('M*')
+ part.content_type = 'image/jpg'
+ part.content_transfer_encoding = 'quoted-printable'
+
+ email.parts << part
+
+ # The Mail patch in `config/initializers/mail_encoding_patch.rb` fixes
+ # encoding of non-binary content. The failure below is expected since we
+ # reverted some upstream changes in order to properly support SMIME signatures
+ # See https://gitlab.com/gitlab-org/gitlab/issues/197386
+ it 'content cannot be decoded back' do
+ # Headers are ok
+ expect(email.subject).to eq(email.subject)
+ expect(email.from).to eq(email.from)
+ expect(email.to).to eq(email.to)
+ expect(email.content_type).to eq(email.content_type)
+ expect(email.content_transfer_encoding).to eq(email.content_transfer_encoding)
+
+ # Content cannot be recovered
+ expect(email.parts.first.decoded).not_to eq(image)
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/reports/components/modal_spec.js b/spec/javascripts/reports/components/modal_spec.js
index d42c509e5b5..ff046e64b6e 100644
--- a/spec/javascripts/reports/components/modal_spec.js
+++ b/spec/javascripts/reports/components/modal_spec.js
@@ -42,8 +42,8 @@ describe('Grouped Test Reports Modal', () => {
);
});
- it('renders miliseconds', () => {
- expect(vm.$el.textContent).toContain(`${modalDataStructure.execution_time.value} ms`);
+ it('renders seconds', () => {
+ expect(vm.$el.textContent).toContain(`${modalDataStructure.execution_time.value} s`);
});
it('render title', () => {
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
index 3962f837a00..b3def474957 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/javascripts/user_popovers_spec.js
@@ -38,6 +38,13 @@ describe('User Popovers', () => {
expect(document.querySelectorAll(selector).length).toBe(popovers.length);
});
+ it('does not initialize the user popovers twice for the same element', () => {
+ const newPopovers = initUserPopovers(document.querySelectorAll(selector));
+ const samePopovers = popovers.every((popover, index) => newPopovers[index] === popover);
+
+ expect(samePopovers).toBe(true);
+ });
+
describe('when user link emits mouseenter event', () => {
let userLink;
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 1f943bebbec..ed763f63756 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -460,6 +460,20 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
end
+ context 'when the deploy token is of group type' do
+ let(:project_with_group) { create(:project, group: create(:group)) }
+ let(:deploy_token) { create(:deploy_token, :group, read_repository: true, groups: [project_with_group.group]) }
+ let(:login) { deploy_token.username }
+
+ subject { gl_auth.find_for_git_client(login, deploy_token.token, project: project_with_group, ip: 'ip') }
+
+ it 'succeeds when login and a group deploy token are valid' do
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project_with_group, :deploy_token, [:download_code, :read_container_image])
+
+ expect(subject).to eq(auth_success)
+ end
+ end
+
context 'when the deploy token has read_registry as a scope' do
let(:deploy_token) { create(:deploy_token, read_repository: false, projects: [project]) }
let(:login) { deploy_token.username }
@@ -469,10 +483,10 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
stub_container_registry_config(enabled: true)
end
- it 'succeeds when login and token are valid' do
+ it 'succeeds when login and a project token are valid' do
auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:read_container_image])
- expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
+ expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
.to eq(auth_success)
end
diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
index a65214fab61..36954252b6b 100644
--- a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
@@ -20,8 +20,14 @@ describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
Gitlab::Email::Smime::Certificate.new(@cert[:key], @cert[:cert])
end
+ let(:mail_body) { "signed hello with Unicode €áø and\r\n newlines\r\n" }
+
let(:mail) do
- ActionMailer::Base.mail(to: 'test@example.com', from: 'info@example.com', body: 'signed hello')
+ ActionMailer::Base.mail(to: 'test@example.com',
+ from: 'info@example.com',
+ content_transfer_encoding: 'quoted-printable',
+ content_type: 'text/plain; charset=UTF-8',
+ body: mail_body)
end
before do
@@ -46,9 +52,16 @@ describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
ca_cert: root_certificate.cert,
signed_data: mail.encoded)
+ # re-verify signature from a new Mail object content
+ # See https://gitlab.com/gitlab-org/gitlab/issues/197386
+ Gitlab::Email::Smime::Signer.verify_signature(
+ cert: certificate.cert,
+ ca_cert: root_certificate.cert,
+ signed_data: Mail.new(mail).encoded)
+
# envelope in a Mail object and obtain the body
decoded_mail = Mail.new(p7enc.data)
- expect(decoded_mail.body.encoded).to eq('signed hello')
+ expect(decoded_mail.body.decoded.dup.force_encoding(decoded_mail.charset)).to eq(mail_body)
end
end
diff --git a/spec/lib/gitlab/tab_width_spec.rb b/spec/lib/gitlab/tab_width_spec.rb
new file mode 100644
index 00000000000..3b5014d27e4
--- /dev/null
+++ b/spec/lib/gitlab/tab_width_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::TabWidth, lib: true do
+ describe '.css_class_for_user' do
+ it 'returns default CSS class when user is nil' do
+ css_class = described_class.css_class_for_user(nil)
+
+ expect(css_class).to eq('tab-width-8')
+ end
+
+ it "returns CSS class for user's tab width", :aggregate_failures do
+ [1, 6, 12].each do |i|
+ user = double('user', tab_width: i)
+ css_class = described_class.css_class_for_user(user)
+
+ expect(css_class).to eq("tab-width-#{i}")
+ end
+ end
+
+ it 'raises if tab width is out of valid range', :aggregate_failures do
+ [0, 13, 'foo', nil].each do |i|
+ expect do
+ user = double('user', tab_width: i)
+ described_class.css_class_for_user(user)
+ end.to raise_error(ArgumentError)
+ end
+ end
+ end
+end
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 5c14d57cf18..568699cf3f6 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -7,6 +7,8 @@ describe DeployToken do
it { is_expected.to have_many :project_deploy_tokens }
it { is_expected.to have_many(:projects).through(:project_deploy_tokens) }
+ it { is_expected.to have_many :group_deploy_tokens }
+ it { is_expected.to have_many(:groups).through(:group_deploy_tokens) }
it_behaves_like 'having unique enum values'
@@ -17,6 +19,29 @@ describe DeployToken do
it { is_expected.to allow_value('GitLab+deploy_token-3.14').for(:username) }
it { is_expected.not_to allow_value('<script>').for(:username).with_message(username_format_message) }
it { is_expected.not_to allow_value('').for(:username).with_message(username_format_message) }
+ it { is_expected.to validate_presence_of(:deploy_token_type) }
+ end
+
+ describe 'deploy_token_type validations' do
+ context 'when a deploy token is associated to a group' do
+ it 'does not allow setting a project to it' do
+ group_token = create(:deploy_token, :group)
+ group_token.projects << build(:project)
+
+ expect(group_token).not_to be_valid
+ expect(group_token.errors.full_messages).to include('Deploy token cannot have projects assigned')
+ end
+ end
+
+ context 'when a deploy token is associated to a project' do
+ it 'does not allow setting a group to it' do
+ project_token = create(:deploy_token)
+ project_token.groups << build(:group)
+
+ expect(project_token).not_to be_valid
+ expect(project_token.errors.full_messages).to include('Deploy token cannot have groups assigned')
+ end
+ end
end
describe '#ensure_token' do
@@ -125,33 +150,148 @@ describe DeployToken do
end
end
+ describe '#holder' do
+ subject { deploy_token.holder }
+
+ context 'when the token is of project type' do
+ it 'returns the relevant holder token' do
+ expect(subject).to eq(deploy_token.project_deploy_tokens.first)
+ end
+ end
+
+ context 'when the token is of group type' do
+ let(:group) { create(:group) }
+ let(:deploy_token) { create(:deploy_token, :group) }
+
+ it 'returns the relevant holder token' do
+ expect(subject).to eq(deploy_token.group_deploy_tokens.first)
+ end
+ end
+ end
+
describe '#has_access_to?' do
let(:project) { create(:project) }
subject { deploy_token.has_access_to?(project) }
- context 'when deploy token is active and related to project' do
- let(:deploy_token) { create(:deploy_token, projects: [project]) }
+ context 'when a project is not passed in' do
+ let(:project) { nil }
- it { is_expected.to be_truthy }
+ it { is_expected.to be_falsy }
end
- context 'when deploy token is active but not related to project' do
- let(:deploy_token) { create(:deploy_token) }
+ context 'when a project is passed in' do
+ context 'when deploy token is active and related to project' do
+ let(:deploy_token) { create(:deploy_token, projects: [project]) }
- it { is_expected.to be_falsy }
- end
+ it { is_expected.to be_truthy }
+ end
- context 'when deploy token is revoked and related to project' do
- let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) }
+ context 'when deploy token is active but not related to project' do
+ let(:deploy_token) { create(:deploy_token) }
- it { is_expected.to be_falsy }
- end
+ it { is_expected.to be_falsy }
+ end
- context 'when deploy token is revoked and not related to the project' do
- let(:deploy_token) { create(:deploy_token, :revoked) }
+ context 'when deploy token is revoked and related to project' do
+ let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) }
- it { is_expected.to be_falsy }
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when deploy token is revoked and not related to the project' do
+ let(:deploy_token) { create(:deploy_token, :revoked) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'and when the token is of group type' do
+ let_it_be(:group) { create(:group) }
+ let(:deploy_token) { create(:deploy_token, :group) }
+
+ before do
+ deploy_token.groups << group
+ end
+
+ context 'and the allow_group_deploy_token feature flag is turned off' do
+ it 'is false' do
+ stub_feature_flags(allow_group_deploy_token: false)
+
+ is_expected.to be_falsy
+ end
+ end
+
+ context 'and the allow_group_deploy_token feature flag is turned on' do
+ before do
+ stub_feature_flags(allow_group_deploy_token: true)
+ end
+
+ context 'and the passed-in project does not belong to any group' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'and the passed-in project belongs to the token group' do
+ it 'is true' do
+ group.projects << project
+
+ is_expected.to be_truthy
+ end
+ end
+
+ context 'and the passed-in project belongs to a subgroup' do
+ let(:child_group) { create(:group, parent_id: group.id) }
+ let(:grandchild_group) { create(:group, parent_id: child_group.id) }
+
+ before do
+ grandchild_group.projects << project
+ end
+
+ context 'and the token group is an ancestor (grand-parent) of this group' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'and the token group is not ancestor of this group' do
+ let(:child2_group) { create(:group, parent_id: group.id) }
+
+ it 'is false' do
+ deploy_token.groups = [child2_group]
+
+ is_expected.to be_falsey
+ end
+ end
+ end
+
+ context 'and the passed-in project does not belong to the token group' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'and the project belongs to a group that is parent of the token group' do
+ let(:super_group) { create(:group) }
+ let(:deploy_token) { create(:deploy_token, :group) }
+ let(:group) { create(:group, parent_id: super_group.id) }
+
+ it 'is false' do
+ super_group.projects << project
+
+ is_expected.to be_falsey
+ end
+ end
+ end
+ end
+
+ context 'and the token is of project type' do
+ let(:deploy_token) { create(:deploy_token, projects: [project]) }
+
+ context 'and the passed-in project is the same as the token project' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'and the passed-in project is not the same as the token project' do
+ subject { deploy_token.has_access_to?(create(:project)) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
end
@@ -183,7 +323,7 @@ describe DeployToken do
end
end
- context 'when passign a value' do
+ context 'when passing a value' do
let(:expires_at) { Date.today + 5.months }
let(:deploy_token) { create(:deploy_token, expires_at: expires_at) }
diff --git a/spec/models/group_deploy_token_spec.rb b/spec/models/group_deploy_token_spec.rb
new file mode 100644
index 00000000000..d38abafa7ed
--- /dev/null
+++ b/spec/models/group_deploy_token_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GroupDeployToken, type: :model do
+ let(:group) { create(:group) }
+ let(:deploy_token) { create(:deploy_token) }
+
+ subject(:group_deploy_token) { create(:group_deploy_token, group: group, deploy_token: deploy_token) }
+
+ it { is_expected.to belong_to :group }
+ it { is_expected.to belong_to :deploy_token }
+
+ it { is_expected.to validate_presence_of :deploy_token }
+ it { is_expected.to validate_presence_of :group }
+ it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:group_id) }
+end
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index bb88983e140..7884b87cc26 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -85,4 +85,19 @@ describe UserPreference do
expect(user_preference.timezone).to eq(Time.zone.tzinfo.name)
end
end
+
+ describe '#tab_width' do
+ it 'is set to 8 by default' do
+ # Intentionally not using factory here to test the constructor.
+ pref = UserPreference.new
+ expect(pref.tab_width).to eq(8)
+ end
+
+ it do
+ is_expected.to validate_numericality_of(:tab_width)
+ .only_integer
+ .is_greater_than_or_equal_to(1)
+ .is_less_than_or_equal_to(12)
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 74e38e79616..855b8e3a8a7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -20,6 +20,9 @@ describe User, :do_not_mock_admin_mode do
describe 'delegations' do
it { is_expected.to delegate_method(:path).to(:namespace).with_prefix }
+
+ it { is_expected.to delegate_method(:tab_width).to(:user_preference) }
+ it { is_expected.to delegate_method(:tab_width=).to(:user_preference).with_arguments(5) }
end
describe 'associations' do
@@ -4126,4 +4129,41 @@ describe User, :do_not_mock_admin_mode do
end
end
end
+
+ describe 'internal methods' do
+ let_it_be(:user) { create(:user) }
+ let!(:ghost) { described_class.ghost }
+ let!(:alert_bot) { described_class.alert_bot }
+ let!(:non_internal) { [user] }
+ let!(:internal) { [ghost, alert_bot] }
+
+ it 'returns non internal users' do
+ expect(described_class.internal).to eq(internal)
+ expect(internal.all?(&:internal?)).to eq(true)
+ end
+
+ it 'returns internal users' do
+ expect(described_class.non_internal).to eq(non_internal)
+ expect(non_internal.all?(&:internal?)).to eq(false)
+ end
+
+ describe '#bot?' do
+ it 'marks bot users' do
+ expect(user.bot?).to eq(false)
+ expect(ghost.bot?).to eq(false)
+
+ expect(alert_bot.bot?).to eq(true)
+ end
+ end
+ end
+
+ describe 'bots & humans' do
+ it 'returns corresponding users' do
+ human = create(:user)
+ bot = create(:user, :bot)
+
+ expect(described_class.humans).to match_array([human])
+ expect(described_class.bots).to match_array([bot])
+ end
+ end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 1a4b8315fde..3b08726c75a 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -559,4 +559,18 @@ describe ProjectPolicy do
end
end
end
+
+ context 'alert bot' do
+ let(:current_user) { User.alert_bot }
+
+ subject { described_class.new(current_user, project) }
+
+ it { is_expected.to be_allowed(:reporter_access) }
+
+ context 'within a private project' do
+ let(:project) { create(:project, :private) }
+
+ it { is_expected.to be_allowed(:admin_issue) }
+ end
+ end
end