diff options
Diffstat (limited to 'spec')
33 files changed, 647 insertions, 434 deletions
diff --git a/spec/controllers/admin/sessions_controller_spec.rb b/spec/controllers/admin/sessions_controller_spec.rb index c1cb57c0b9d..bd0bb0bd81f 100644 --- a/spec/controllers/admin/sessions_controller_spec.rb +++ b/spec/controllers/admin/sessions_controller_spec.rb @@ -17,7 +17,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do get :new expect(response).to have_gitlab_http_status(:not_found) - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) end end @@ -28,7 +28,21 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do get :new expect(response).to render_template :new - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) + end + + context 'already in admin mode' do + before do + controller.current_user_mode.request_admin_mode! + controller.current_user_mode.enable_admin_mode!(password: user.password) + end + + it 'redirects to original location' do + get :new + + expect(response).to redirect_to(admin_root_path) + expect(controller.current_user_mode.admin_mode?).to be(true) + end end end end @@ -39,7 +53,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do post :create expect(response).to have_gitlab_http_status(:not_found) - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) end end @@ -47,24 +61,60 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do let(:user) { create(:admin) } it 'sets admin mode with a valid password' do - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) controller.store_location_for(:redirect, admin_root_path) + + # triggering the auth form will request admin mode + get :new + post :create, params: { password: user.password } expect(response).to redirect_to admin_root_path - expect(controller.send(:current_user_mode).admin_mode?).to be(true) + expect(controller.current_user_mode.admin_mode?).to be(true) end it 'fails with an invalid password' do - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) controller.store_location_for(:redirect, admin_root_path) + # triggering the auth form will request admin mode + get :new + post :create, params: { password: '' } expect(response).to render_template :new - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) + end + + it 'fails if not requested first' do + expect(controller.current_user_mode.admin_mode?).to be(false) + + controller.store_location_for(:redirect, admin_root_path) + + # do not trigger the auth form + + post :create, params: { password: user.password } + + expect(response).to redirect_to(new_admin_session_path) + expect(controller.current_user_mode.admin_mode?).to be(false) + end + + it 'fails if request period expired' do + expect(controller.current_user_mode.admin_mode?).to be(false) + + controller.store_location_for(:redirect, admin_root_path) + + # triggering the auth form will request admin mode + get :new + + Timecop.freeze(Gitlab::Auth::CurrentUserMode::ADMIN_MODE_REQUESTED_GRACE_PERIOD.from_now) do + post :create, params: { password: user.password } + + expect(response).to redirect_to(new_admin_session_path) + expect(controller.current_user_mode.admin_mode?).to be(false) + end end end end @@ -75,7 +125,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do get :destroy expect(response).to have_gitlab_http_status(404) - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) end end @@ -83,15 +133,17 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do let(:user) { create(:admin) } it 'disables admin mode and redirects to main page' do - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) + + get :new post :create, params: { password: user.password } - expect(controller.send(:current_user_mode).admin_mode?).to be(true) + expect(controller.current_user_mode.admin_mode?).to be(true) get :destroy expect(response).to have_gitlab_http_status(:found) expect(response).to redirect_to(root_path) - expect(controller.send(:current_user_mode).admin_mode?).to be(false) + expect(controller.current_user_mode.admin_mode?).to be(false) end end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 9c6ea13f948..e72ab16f62a 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -814,6 +814,7 @@ describe ApplicationController do context 'that re-authenticated' do before do + Gitlab::Auth::CurrentUserMode.new(user).request_admin_mode! Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(password: user.password) end diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index df836c2c3e3..270a2fcc1d6 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -62,6 +62,12 @@ describe Oauth::ApplicationsController do end end + context 'Helpers' do + it 'current_user_mode available' do + expect(subject.current_user_mode).not_to be_nil + end + end + def disable_user_oauth allow(Gitlab::CurrentSettings.current_application_settings).to receive(:user_oauth_applications?).and_return(false) end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 521dbe7ee23..6c5f36804e8 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe OmniauthCallbacksController, type: :controller do +describe OmniauthCallbacksController, type: :controller, do_not_mock_admin_mode: true do include LoginHelpers describe 'omniauth' do @@ -336,4 +336,109 @@ describe OmniauthCallbacksController, type: :controller do end end end + + describe 'enable admin mode' do + include_context 'custom session' + + let(:provider) { :auth0 } + let(:extern_uid) { 'my-uid' } + let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) } + + def reauthenticate_and_check_admin_mode(expected_admin_mode:) + # Initially admin mode disabled + expect(subject.current_user_mode.admin_mode?).to be(false) + + # Trigger OmniAuth admin mode flow and expect admin mode status + post provider + + expect(request.env['warden']).to be_authenticated + expect(subject.current_user_mode.admin_mode?).to be(expected_admin_mode) + end + + context 'user and admin mode requested by the same user' do + before do + sign_in user + + mock_auth_hash(provider.to_s, extern_uid, user.email, additional_info: {}) + stub_omniauth_provider(provider, context: request) + end + + context 'with a regular user' do + it 'cannot be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: false) + + expect(response).to redirect_to(root_path) + end + end + + context 'with an admin user' do + let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) } + + context 'when requested first' do + before do + subject.current_user_mode.request_admin_mode! + end + + it 'can be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: true) + + expect(response).to redirect_to(admin_root_path) + end + end + + context 'when not requested first' do + it 'cannot be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: false) + + expect(response).to redirect_to(root_path) + end + end + end + end + + context 'user and admin mode requested by different users' do + let(:reauth_extern_uid) { 'another_uid' } + let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider) } + + before do + sign_in user + + mock_auth_hash(provider.to_s, reauth_extern_uid, reauth_user.email, additional_info: {}) + stub_omniauth_provider(provider, context: request) + end + + context 'with a regular user' do + it 'cannot be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: false) + + expect(response).to redirect_to(profile_account_path) + end + end + + context 'with an admin user' do + let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) } + let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider, access_level: :admin) } + + context 'when requested first' do + before do + subject.current_user_mode.request_admin_mode! + end + + it 'cannot be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: false) + + expect(response).to redirect_to(new_admin_session_path) + end + end + + context 'when not requested first' do + it 'cannot be enabled' do + reauthenticate_and_check_admin_mode(expected_admin_mode: false) + + expect(response).to redirect_to(profile_account_path) + end + end + end + end + end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index ba7374d5040..741f46cef45 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe 'Gcp Cluster', :js do +describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do include GoogleApi::CloudPlatformHelpers let(:project) { create(:project) } diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 382b5f3cac0..01687674309 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -49,11 +49,11 @@ describe 'Environments page', :js do it 'renders second page of pipelines' do visit_environments(project, scope: 'available') - find('.js-next-button').click + find('.page-link.next-page-item').click wait_for_requests - expect(page).to have_selector('.gl-pagination .page', count: 2) - expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2") + expect(page).to have_selector('.gl-pagination .page-link', count: 4) + expect(find('.gl-pagination .page-link.active').text).to eq("2") end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f5558a1f2ec..b4c9eb7ebec 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -592,15 +592,15 @@ describe 'Pipelines', :js do visit project_pipelines_path(project, page: '2') wait_for_requests - expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(page).to have_selector('.gl-pagination .page-link', count: 4) end it 'shows updated content' do visit project_pipelines_path(project) wait_for_requests - page.find('.js-next-button .page-link').click + page.find('.page-link.next-page-item').click - expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(page).to have_selector('.gl-pagination .page-link', count: 4) end end end diff --git a/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js b/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js index 7ba35358442..c9cdd728509 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import $ from 'jquery'; import { GlIcon } from '@gitlab/ui'; import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue'; @@ -169,4 +170,14 @@ describe('ClusterFormDropdown', () => { expect(vm.findAll('.js-dropdown-item').length).toEqual(1); expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name); }); + + it('focuses dropdown search input when dropdown is displayed', () => { + const dropdownEl = vm.find('.dropdown').element; + + expect(vm.find(DropdownSearchInput).props('focused')).toBe(false); + + $(dropdownEl).trigger('shown.bs.dropdown'); + + expect(vm.find(DropdownSearchInput).props('focused')).toBe(true); + }); }); diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js new file mode 100644 index 00000000000..0d0e4ae4349 --- /dev/null +++ b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js @@ -0,0 +1,55 @@ +import { mount } from '@vue/test-utils'; +import DropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; + +describe('DropdownSearchInputComponent', () => { + let wrapper; + + const defaultProps = { + placeholderText: 'Search something', + }; + const buildVM = (propsData = defaultProps) => { + wrapper = mount(DropdownSearchInputComponent, { + propsData, + }); + }; + const findInputEl = () => wrapper.find('.dropdown-input-field'); + + beforeEach(() => { + buildVM(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('template', () => { + it('renders input element with type `search`', () => { + expect(findInputEl().exists()).toBe(true); + expect(findInputEl().attributes('type')).toBe('search'); + }); + + it('renders search icon element', () => { + expect(wrapper.find('.fa-search.dropdown-input-search').exists()).toBe(true); + }); + + it('renders clear search icon element', () => { + expect(wrapper.find('.fa-times.dropdown-input-clear.js-dropdown-input-clear').exists()).toBe( + true, + ); + }); + + it('displays custom placeholder text', () => { + expect(findInputEl().attributes('placeholder')).toBe(defaultProps.placeholderText); + }); + + it('focuses input element when focused property equals true', () => { + const inputEl = findInputEl().element; + + jest.spyOn(inputEl, 'focus'); + + wrapper.setProps({ focused: true }); + + expect(inputEl.focus).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/table_pagination_spec.js b/spec/frontend/vue_shared/components/table_pagination_spec.js index 0a9ff36b2fb..8105d1fcef3 100644 --- a/spec/frontend/vue_shared/components/table_pagination_spec.js +++ b/spec/frontend/vue_shared/components/table_pagination_spec.js @@ -1,5 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; +import { GlPagination } from '@gitlab/ui'; describe('Pagination component', () => { let wrapper; @@ -12,15 +13,6 @@ describe('Pagination component', () => { }); }; - const findFirstButtonLink = () => wrapper.find('.js-first-button .page-link'); - const findPreviousButton = () => wrapper.find('.js-previous-button'); - const findPreviousButtonLink = () => wrapper.find('.js-previous-button .page-link'); - const findNextButton = () => wrapper.find('.js-next-button'); - const findNextButtonLink = () => wrapper.find('.js-next-button .page-link'); - const findLastButtonLink = () => wrapper.find('.js-last-button .page-link'); - const findPages = () => wrapper.findAll('.page'); - const findSeparator = () => wrapper.find('.separator'); - beforeEach(() => { spy = jest.fn(); }); @@ -46,290 +38,54 @@ describe('Pagination component', () => { expect(wrapper.isEmpty()).toBe(true); }); - describe('prev button', () => { - it('should be disabled and non clickable', () => { - mountComponent({ - pageInfo: { - nextPage: 2, - page: 1, - perPage: 20, - previousPage: NaN, - total: 84, - totalPages: 5, - }, - change: spy, - }); - - expect(findPreviousButton().classes()).toContain('disabled'); - findPreviousButtonLink().trigger('click'); - expect(spy).not.toHaveBeenCalled(); - }); - - it('should be disabled and non clickable when total and totalPages are NaN', () => { - mountComponent({ - pageInfo: { - nextPage: 2, - page: 1, - perPage: 20, - previousPage: NaN, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - expect(findPreviousButton().classes()).toContain('disabled'); - findPreviousButtonLink().trigger('click'); - expect(spy).not.toHaveBeenCalled(); - }); - - it('should be enabled and clickable', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: 84, - totalPages: 5, - }, - change: spy, - }); - findPreviousButtonLink().trigger('click'); - expect(spy).toHaveBeenCalledWith(1); - }); - - it('should be enabled and clickable when total and totalPages are NaN', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - findPreviousButtonLink().trigger('click'); - expect(spy).toHaveBeenCalledWith(1); - }); - }); - - describe('first button', () => { - it('should call the change callback with the first page', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: 84, - totalPages: 5, - }, - change: spy, - }); - const button = findFirstButtonLink(); - expect(button.text().trim()).toEqual('« First'); - button.trigger('click'); - expect(spy).toHaveBeenCalledWith(1); - }); - - it('should call the change callback with the first page when total and totalPages are NaN', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - const button = findFirstButtonLink(); - expect(button.text().trim()).toEqual('« First'); - button.trigger('click'); - expect(spy).toHaveBeenCalledWith(1); - }); - }); - - describe('last button', () => { - it('should call the change callback with the last page', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: 84, - totalPages: 5, - }, - change: spy, - }); - const button = findLastButtonLink(); - expect(button.text().trim()).toEqual('Last »'); - button.trigger('click'); - expect(spy).toHaveBeenCalledWith(5); - }); - - it('should not render', () => { - mountComponent({ - pageInfo: { - nextPage: 3, - page: 2, - perPage: 20, - previousPage: 1, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - expect(findLastButtonLink().exists()).toBe(false); - }); - }); - - describe('next button', () => { - it('should be disabled and non clickable', () => { - mountComponent({ - pageInfo: { - nextPage: NaN, - page: 5, - perPage: 20, - previousPage: 4, - total: 84, - totalPages: 5, - }, - change: spy, - }); - expect( - findNextButton() - .text() - .trim(), - ).toEqual('Next ›'); - findNextButtonLink().trigger('click'); - expect(spy).not.toHaveBeenCalled(); - }); - - it('should be disabled and non clickable when total and totalPages are NaN', () => { - mountComponent({ - pageInfo: { - nextPage: NaN, - page: 5, - perPage: 20, - previousPage: 4, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - expect( - findNextButton() - .text() - .trim(), - ).toEqual('Next ›'); - findNextButtonLink().trigger('click'); - expect(spy).not.toHaveBeenCalled(); - }); - - it('should be enabled and clickable', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: 84, - totalPages: 5, - }, - change: spy, - }); - findNextButtonLink().trigger('click'); - expect(spy).toHaveBeenCalledWith(4); + it('renders if there is a next page', () => { + mountComponent({ + pageInfo: { + nextPage: 2, + page: 1, + perPage: 20, + previousPage: NaN, + total: 15, + totalPages: 1, + }, + change: spy, }); - it('should be enabled and clickable when total and totalPages are NaN', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - findNextButtonLink().trigger('click'); - expect(spy).toHaveBeenCalledWith(4); - }); + expect(wrapper.isEmpty()).toBe(false); }); - describe('numbered buttons', () => { - it('should render 5 pages', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: 84, - totalPages: 5, - }, - change: spy, - }); - expect(findPages().length).toEqual(5); + it('renders if there is a prev page', () => { + mountComponent({ + pageInfo: { + nextPage: NaN, + page: 2, + perPage: 20, + previousPage: 1, + total: 15, + totalPages: 1, + }, + change: spy, }); - it('should not render any page', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - expect(findPages().length).toEqual(0); - }); + expect(wrapper.isEmpty()).toBe(false); }); + }); - describe('spread operator', () => { - it('should render', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: 84, - totalPages: 10, - }, - change: spy, - }); - expect( - findSeparator() - .text() - .trim(), - ).toEqual('...'); - }); - - it('should not render', () => { - mountComponent({ - pageInfo: { - nextPage: 4, - page: 3, - perPage: 20, - previousPage: 2, - total: NaN, - totalPages: NaN, - }, - change: spy, - }); - expect(findSeparator().exists()).toBe(false); + describe('events', () => { + it('calls change method when page changes', () => { + mountComponent({ + pageInfo: { + nextPage: NaN, + page: 2, + perPage: 20, + previousPage: 1, + total: 15, + totalPages: 1, + }, + change: spy, }); + wrapper.find(GlPagination).vm.$emit('input', 3); + expect(spy).toHaveBeenCalledWith(3); }); }); }); diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 5d1a5fe1987..a3c51f24307 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -25,7 +25,7 @@ describe GitlabSchema.types['Project'] do issue pipelines removeSourceBranchAfterMerge sentryDetailedError snippets ] - is_expected.to have_graphql_fields(*expected_fields) + is_expected.to include_graphql_fields(*expected_fields) end describe 'issue field' do diff --git a/spec/helpers/container_expiration_policies_helper_spec.rb b/spec/helpers/container_expiration_policies_helper_spec.rb new file mode 100644 index 00000000000..3eb1234d82b --- /dev/null +++ b/spec/helpers/container_expiration_policies_helper_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContainerExpirationPoliciesHelper do + describe '#keep_n_options' do + it 'returns keep_n options formatted for dropdown usage' do + expected_result = [ + { key: 1, label: '1 tag per image name' }, + { key: 5, label: '5 tags per image name' }, + { key: 10, label: '10 tags per image name' }, + { key: 25, label: '25 tags per image name' }, + { key: 50, label: '50 tags per image name' }, + { key: 100, label: '100 tags per image name' } + ] + + expect(helper.keep_n_options).to eq(expected_result) + end + end + + describe '#cadence_options' do + it 'returns cadence options formatted for dropdown usage' do + expected_result = [ + { key: '1d', label: 'Every day' }, + { key: '7d', label: 'Every week' }, + { key: '14d', label: 'Every two weeks' }, + { key: '1month', label: 'Every month' }, + { key: '3month', label: 'Every three months' } + ] + + expect(helper.cadence_options).to eq(expected_result) + end + end + + describe '#older_than_options' do + it 'returns older_than options formatted for dropdown usage' do + expected_result = [ + { key: '7d', label: '7 days until tags are automatically removed' }, + { key: '14d', label: '14 days until tags are automatically removed' }, + { key: '30d', label: '30 days until tags are automatically removed' }, + { key: '90d', label: '90 days until tags are automatically removed' } + ] + + expect(helper.older_than_options).to eq(expected_result) + end + end +end diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index 882a125a0da..8d7572c5b5f 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -42,6 +42,7 @@ describe NavHelper, :do_not_mock_admin_mode do context 'with admin mode enabled' do before do + current_user_mode.request_admin_mode! current_user_mode.enable_admin_mode!(password: user.password) end @@ -62,6 +63,7 @@ describe NavHelper, :do_not_mock_admin_mode do context 'with admin mode enabled' do before do + current_user_mode.request_admin_mode! current_user_mode.enable_admin_mode!(password: user.password) end @@ -89,11 +91,18 @@ describe NavHelper, :do_not_mock_admin_mode do end end - it 'returns only the sign in and search when the user is not logged in' do - allow(helper).to receive(:current_user).and_return(nil) - allow(helper).to receive(:can?).with(nil, :read_cross_project) { true } + context 'when the user is not logged in' do + let(:current_user_mode) { Gitlab::Auth::CurrentUserMode.new(nil) } - expect(helper.header_links).to contain_exactly(:sign_in, :search) + before do + allow(helper).to receive(:current_user).and_return(nil) + allow(helper).to receive(:current_user_mode).and_return(current_user_mode) + allow(helper).to receive(:can?).with(nil, :read_cross_project) { true } + end + + it 'returns only the sign in and search when the user is not logged in' do + expect(helper.header_links).to contain_exactly(:sign_in, :search) + end end end diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index e2bbada3a49..29bdf05b8cf 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -83,7 +83,7 @@ describe('Pipelines table in Commits and Merge requests', function() { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button .page-link').click(); + vm.$el.querySelector('.next-page-item').click(); expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); done(); diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 9c8da4970f4..75526c2ba74 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -92,13 +92,13 @@ describe('Environment', () => { describe('pagination', () => { it('should render pagination', () => { - expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(5); + expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(9); }); it('should make an API request when page is clicked', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click(); + component.$el.querySelector('.gl-pagination li:nth-child(3) .page-link').click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); done(); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index d217bbb3078..6530201240f 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -115,7 +115,9 @@ describe('Environments Folder View', () => { it('should make an API request when changing page', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination .js-last-button .page-link').click(); + component.$el + .querySelector('.gl-pagination .page-item:nth-last-of-type(2) .page-link') + .click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index e1123cc7248..5cd91413c5f 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -446,7 +446,7 @@ describe('Pipelines', () => { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button .page-link').click(); + vm.$el.querySelector('.next-page-item').click(); expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' }); diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js deleted file mode 100644 index 456f310d10c..00000000000 --- a/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js +++ /dev/null @@ -1,51 +0,0 @@ -import Vue from 'vue'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import dropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; - -const componentConfig = { - placeholderText: 'Search something', -}; - -const createComponent = (config = componentConfig) => { - const Component = Vue.extend(dropdownSearchInputComponent); - - return mountComponent(Component, config); -}; - -describe('DropdownSearchInputComponent', () => { - let vm; - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('template', () => { - it('renders input element with type `search`', () => { - const inputEl = vm.$el.querySelector('input.dropdown-input-field'); - - expect(inputEl).not.toBeNull(); - expect(inputEl.getAttribute('type')).toBe('search'); - }); - - it('renders search icon element', () => { - expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull(); - }); - - it('renders clear search icon element', () => { - expect( - vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'), - ).not.toBeNull(); - }); - - it('displays custom placeholder text', () => { - const inputEl = vm.$el.querySelector('input.dropdown-input-field'); - - expect(inputEl.getAttribute('placeholder')).toBe(componentConfig.placeholderText); - }); - }); -}); diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 125039edcf8..3d10f411310 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Gitlab::Auth::UserAuthFinders do +describe Gitlab::Auth::AuthFinders do include described_class let(:user) { create(:user) } @@ -196,13 +196,13 @@ describe Gitlab::Auth::UserAuthFinders do context 'when validate_access_token! returns valid' do it 'returns user' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(find_user_from_access_token).to eq user end it 'returns exception if token has no user' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil) expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) @@ -228,7 +228,7 @@ describe Gitlab::Auth::UserAuthFinders do let(:personal_access_token) { create(:personal_access_token, user: user) } before do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'returns exception if token has no user' do @@ -279,7 +279,7 @@ describe Gitlab::Auth::UserAuthFinders do context 'passed as header' do it 'returns token if valid personal_access_token' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(find_personal_access_token).to eq personal_access_token end @@ -287,7 +287,7 @@ describe Gitlab::Auth::UserAuthFinders do context 'passed as param' do it 'returns token if valid personal_access_token' do - set_param(Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_PARAM, personal_access_token.token) + set_param(described_class::PRIVATE_TOKEN_PARAM, personal_access_token.token) expect(find_personal_access_token).to eq personal_access_token end @@ -298,7 +298,7 @@ describe Gitlab::Auth::UserAuthFinders do end it 'returns exception if invalid personal_access_token' do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid_token' + env[described_class::PRIVATE_TOKEN_HEADER] = 'invalid_token' expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) end @@ -379,4 +379,58 @@ describe Gitlab::Auth::UserAuthFinders do end end end + + describe '#find_runner_from_token' do + let(:runner) { create(:ci_runner) } + + context 'with API requests' do + before do + env['SCRIPT_NAME'] = '/api/endpoint' + end + + it 'returns the runner if token is valid' do + set_param(:token, runner.token) + + expect(find_runner_from_token).to eq(runner) + end + + it 'returns nil if token is not present' do + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if token is blank' do + set_param(:token, '') + + expect(find_runner_from_token).to be_nil + end + + it 'returns exception if invalid token' do + set_param(:token, 'invalid_token') + + expect { find_runner_from_token }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + end + + context 'without API requests' do + before do + env['SCRIPT_NAME'] = 'url.ics' + end + + it 'returns nil if token is valid' do + set_param(:token, runner.token) + + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if token is blank' do + expect(find_runner_from_token).to be_nil + end + + it 'returns nil if invalid token' do + set_param(:token, 'invalid_token') + + expect(find_runner_from_token).to be_nil + end + end + end end diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb index b93d460cf48..3b3db0f7315 100644 --- a/spec/lib/gitlab/auth/current_user_mode_spec.rb +++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb @@ -62,69 +62,90 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do context 'when the user is an admin' do let(:user) { build(:user, :admin) } - it 'is false by default' do - expect(subject.admin_mode?).to be(false) - end - - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(password: nil) - - expect(subject.admin_mode?).to be(false) - end + context 'when admin mode not requested' do + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - it 'can be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password) + it 'raises exception if we try to enable it' do + expect do + subject.enable_admin_mode!(password: user.password) + end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError) - expect(subject.admin_mode?).to be(true) + expect(subject.admin_mode?).to be(false) + end end - it 'can be disabled' do - subject.enable_admin_mode!(password: user.password) - subject.disable_admin_mode! - - expect(subject.admin_mode?).to be(false) - end + context 'when admin mode requested first' do + before do + subject.request_admin_mode! + end - it 'will expire in the future' do - subject.enable_admin_mode!(password: user.password) - expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do - # in the future this will be a new request, simulate by clearing the RequestStore - Gitlab::SafeRequestStore.clear! + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(password: nil) - expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' + expect(subject.admin_mode?).to be(false) end - end - context 'skipping password validation' do it 'can be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + subject.enable_admin_mode!(password: user.password) expect(subject.admin_mode?).to be(true) end - it 'can be enabled with an invalid password' do - subject.enable_admin_mode!(skip_password_validation: true) + it 'can be disabled' do + subject.enable_admin_mode!(password: user.password) + subject.disable_admin_mode! - expect(subject.admin_mode?).to be(true) + expect(subject.admin_mode?).to be(false) end - end - context 'with two independent sessions' do - let(:another_session) { {} } - let(:another_subject) { described_class.new(user) } + it 'will expire in the future' do + subject.enable_admin_mode!(password: user.password) + expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present' - before do - allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) + Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do + # in the future this will be a new request, simulate by clearing the RequestStore + Gitlab::SafeRequestStore.clear! + + expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future' + end end - it 'can be enabled in one and seen in the other' do - Gitlab::Session.with_session(another_session) do - another_subject.enable_admin_mode!(password: user.password) + context 'skipping password validation' do + it 'can be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + + expect(subject.admin_mode?).to be(true) end - expect(subject.admin_mode?).to be(true) + it 'can be enabled with an invalid password' do + subject.enable_admin_mode!(skip_password_validation: true) + + expect(subject.admin_mode?).to be(true) + end + end + + context 'with two independent sessions' do + let(:another_session) { {} } + let(:another_subject) { described_class.new(user) } + + before do + allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session]) + end + + it 'can be enabled in one and seen in the other' do + Gitlab::Session.with_session(another_session) do + another_subject.request_admin_mode! + another_subject.enable_admin_mode!(password: user.password) + end + + expect(subject.admin_mode?).to be(true) + end end end end @@ -134,16 +155,28 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do let(:user) { build(:user, :admin) } it 'creates a timestamp in the session' do + subject.request_admin_mode! subject.enable_admin_mode!(password: user.password) expect(session).to include(expected_session_entry(be_within(1.second).of Time.now)) end end + describe '#enable_sessionless_admin_mode!' do + let(:user) { build(:user, :admin) } + + it 'enabled admin mode without password' do + subject.enable_sessionless_admin_mode! + + expect(subject.admin_mode?).to be(true) + end + end + describe '#disable_admin_mode!' do let(:user) { build(:user, :admin) } it 'sets the session timestamp to nil' do + subject.request_admin_mode! subject.disable_admin_mode! expect(session).to include(expected_session_entry(be_nil)) diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb index f7fff389d88..4dbcd0df302 100644 --- a/spec/lib/gitlab/auth/request_authenticator_spec.rb +++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb @@ -66,4 +66,28 @@ describe Gitlab::Auth::RequestAuthenticator do expect(subject.find_sessionless_user([:api])).to be_blank end end + + describe '#runner' do + let!(:runner) { build(:ci_runner) } + + it 'returns the runner using #find_runner_from_token' do + expect_any_instance_of(described_class) + .to receive(:find_runner_from_token) + .and_return(runner) + + expect(subject.runner).to eq runner + end + + it 'returns nil if no runner is found' do + expect(subject.runner).to be_blank + end + + it 'rescue Gitlab::Auth::AuthenticationError exceptions' do + expect_any_instance_of(described_class) + .to receive(:find_runner_from_token) + .and_raise(Gitlab::Auth::UnauthorizedError) + + expect(subject.runner).to be_blank + end + end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 26793f28bd8..8d436fb28e0 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -443,6 +443,7 @@ project: - downstream_project_subscriptions - service_desk_setting - import_failures +- container_expiration_policy award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index fa6bf14bf64..bf8c079f027 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -773,3 +773,13 @@ ZoomMeeting: ServiceDeskSetting: - project_id - issue_template_key +ContainerExpirationPolicy: +- created_at +- updated_at +- next_run_at +- project_id +- name_regex +- cadence +- older_than +- keep_n +- enabled diff --git a/spec/models/container_expiration_policy_spec.rb b/spec/models/container_expiration_policy_spec.rb new file mode 100644 index 00000000000..1ce76490448 --- /dev/null +++ b/spec/models/container_expiration_policy_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerExpirationPolicy, type: :model do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + + describe '#enabled' do + it { is_expected.to allow_value(true).for(:enabled) } + it { is_expected.to allow_value(false).for(:enabled) } + it { is_expected.not_to allow_value(nil).for(:enabled) } + end + + describe '#cadence' do + it { is_expected.to validate_presence_of(:cadence) } + + it { is_expected.to allow_value('1d').for(:cadence) } + it { is_expected.to allow_value('1month').for(:cadence) } + it { is_expected.not_to allow_value('123asdf').for(:cadence) } + it { is_expected.not_to allow_value(nil).for(:cadence) } + end + + describe '#older_than' do + it { is_expected.to allow_value('7d').for(:older_than) } + it { is_expected.to allow_value('14d').for(:older_than) } + it { is_expected.to allow_value(nil).for(:older_than) } + it { is_expected.not_to allow_value('123asdf').for(:older_than) } + end + + describe '#keep_n' do + it { is_expected.to allow_value(10).for(:keep_n) } + it { is_expected.to allow_value(nil).for(:keep_n) } + it { is_expected.not_to allow_value('foo').for(:keep_n) } + end + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index c93e6aafd75..3089afd8d8a 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -199,6 +199,13 @@ describe Namespace do expect(described_class.find_by_pages_host(host)).to eq(namespace) end + + it "returns no result if the provided host is not subdomain of the Pages host" do + create(:namespace, name: 'namespace.io') + host = "namespace.io" + + expect(described_class.find_by_pages_host(host)).to eq(nil) + end end describe '#ancestors_upto' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e7abdf847e1..feb06f4ffc9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -62,6 +62,7 @@ describe Project do it { is_expected.to have_one(:external_wiki_service) } it { is_expected.to have_one(:project_feature) } it { is_expected.to have_one(:project_repository) } + it { is_expected.to have_one(:container_expiration_policy) } it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') } it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:last_event).class_name('Event') } @@ -137,6 +138,13 @@ describe Project do expect(project.ci_cd_settings).to be_persisted end + it 'automatically creates a container expiration policy row' do + project = create(:project) + + expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy) + expect(project.container_expiration_policy).to be_persisted + end + it 'automatically creates a Pages metadata row' do project = create(:project) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4f9dfbd9103..9e8aa6a95e8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2839,6 +2839,7 @@ describe User, :do_not_mock_admin_mode do context 'when admin mode is enabled' do before do + Gitlab::Auth::CurrentUserMode.new(user).request_admin_mode! Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(password: user.password) end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index bbfe40041a1..0c53c04ba40 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -146,13 +146,13 @@ describe API::Helpers do let(:personal_access_token) { create(:personal_access_token, user: user) } it "returns a 401 response for an invalid token" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid token' + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = 'invalid token' expect { current_user }.to raise_error /401/ end it "returns a 403 response for a user without access" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) expect { current_user }.to raise_error /403/ @@ -160,7 +160,7 @@ describe API::Helpers do it 'returns a 403 response for a user who is blocked' do user.block! - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error /403/ end @@ -168,7 +168,7 @@ describe API::Helpers do context 'when terms are enforced' do before do enforce_terms - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'returns a 403 when a user has not accepted the terms' do @@ -183,27 +183,27 @@ describe API::Helpers do end it "sets current_user" do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect(current_user).to eq(user) end it "does not allow tokens without the appropriate scope" do personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user']) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::InsufficientScopeError end it 'does not allow revoked tokens' do personal_access_token.revoke! - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::RevokedError end it 'does not allow expired tokens' do personal_access_token.update!(expires_at: 1.day.ago) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token expect { current_user }.to raise_error Gitlab::Auth::ExpiredError end @@ -213,7 +213,7 @@ describe API::Helpers do before do stub_config_setting(impersonation_enabled: false) - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token end it 'does not allow impersonation tokens' do @@ -478,7 +478,7 @@ describe API::Helpers do context 'passed as param' do before do - set_param(Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_PARAM, token.token) + set_param(Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_PARAM, token.token) end it_behaves_like 'sudo' @@ -486,7 +486,7 @@ describe API::Helpers do context 'passed as header' do before do - env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = token.token + env[Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER] = token.token end it_behaves_like 'sudo' diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 59113687d1c..9e62d84d313 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -100,6 +100,18 @@ describe 'Rack Attack global throttles' do end end + context 'when the request is authenticated by a runner token' do + let(:request_jobs_url) { '/api/v4/jobs/request' } + let(:runner) { create(:ci_runner) } + + it 'does not cont as unauthenticated' do + (1 + requests_per_period).times do + post request_jobs_url, params: { token: runner.token } + expect(response).to have_http_status 204 + end + end + end + it 'logs RackAttack info into structured logs' do requests_per_period.times do get url_that_does_not_require_authentication diff --git a/spec/support/helpers/admin_mode_helpers.rb b/spec/support/helpers/admin_mode_helpers.rb index de8ffe40536..e995a7d4f5e 100644 --- a/spec/support/helpers/admin_mode_helpers.rb +++ b/spec/support/helpers/admin_mode_helpers.rb @@ -3,7 +3,7 @@ # Helper for enabling admin mode in tests module AdminModeHelper - # Users are logged in by default in user mode and have to switch to admin + # Administrators are logged in by default in user mode and have to switch to admin # mode for accessing any administrative functionality. This helper lets a user # be in admin mode without requiring a second authentication step (provided # the user is an admin) diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index ebba5d8a73c..dbf457a9200 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -28,6 +28,19 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| end end +RSpec::Matchers.define :include_graphql_fields do |*expected| + expected_field_names = expected.map { |name| GraphqlHelpers.fieldnamerize(name) } + + match do |kls| + expect(kls.fields.keys).to include(*expected_field_names) + end + + failure_message do |kls| + missing = expected_field_names - kls.fields.keys + "is missing fields: <#{missing.inspect}>" if missing.any? + end +end + RSpec::Matchers.define :have_graphql_field do |field_name, args = {}| match do |kls| field = kls.fields[GraphqlHelpers.fieldnamerize(field_name)] diff --git a/spec/views/admin/sessions/new.html.haml_spec.rb b/spec/views/admin/sessions/new.html.haml_spec.rb index 57255748988..b3208296c80 100644 --- a/spec/views/admin/sessions/new.html.haml_spec.rb +++ b/spec/views/admin/sessions/new.html.haml_spec.rb @@ -3,29 +3,44 @@ require 'spec_helper' describe 'admin/sessions/new.html.haml' do - context 'admin has password set' do - before do - allow(view).to receive(:password_authentication_enabled_for_web?).and_return(true) - end + let(:user) { create(:admin) } + + before do + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:omniauth_enabled?).and_return(false) + end - it "shows enter password form" do + context 'internal admin user' do + it 'shows enter password form' do render expect(rendered).to have_css('#login-pane.active') expect(rendered).to have_selector('input[name="password"]') end + + it 'warns authentication not possible if password not set' do + allow(user).to receive(:require_password_creation_for_web?).and_return(true) + + render + + expect(rendered).not_to have_css('#login-pane') + expect(rendered).to have_content _('No authentication methods configured.') + end end - context 'admin has no password set' do + context 'omniauth authentication enabled' do before do - allow(view).to receive(:password_authentication_enabled_for_web?).and_return(false) + allow(view).to receive(:omniauth_enabled?).and_return(true) + allow(view).to receive(:button_based_providers_enabled?).and_return(true) end - it "warns authentication not possible" do + it 'shows omniauth form' do render - expect(rendered).not_to have_css('#login-pane') - expect(rendered).to have_content 'No authentication methods configured' + expect(rendered).to have_css('.omniauth-container') + expect(rendered).to have_content _('Sign in with') + + expect(rendered).not_to have_content _('No authentication methods configured.') end end end diff --git a/spec/views/layouts/application.html.haml_spec.rb b/spec/views/layouts/application.html.haml_spec.rb index bdd4a97a1f5..4270bbf1924 100644 --- a/spec/views/layouts/application.html.haml_spec.rb +++ b/spec/views/layouts/application.html.haml_spec.rb @@ -11,6 +11,7 @@ describe 'layouts/application' do allow(view).to receive(:session).and_return({}) allow(view).to receive(:user_signed_in?).and_return(true) allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user)) end context 'body data elements for pageview context' do |