diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 12:09:00 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 12:09:00 +0000 |
commit | 88a0824944720b6edaaef56376713541b9a02118 (patch) | |
tree | f5fcc4f9755f249779cda9a8f02902d734af6e7e /spec | |
parent | 7d19df2d34a9803d9f077c16315ba919b7ae2aa2 (diff) | |
download | gitlab-ce-88a0824944720b6edaaef56376713541b9a02118.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
31 files changed, 910 insertions, 461 deletions
diff --git a/spec/features/admin/admin_mode/workers_spec.rb b/spec/features/admin/admin_mode/workers_spec.rb new file mode 100644 index 00000000000..e33c9d7e64c --- /dev/null +++ b/spec/features/admin/admin_mode/workers_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Test an operation that triggers background jobs requiring administrative rights +describe 'Admin mode for workers', :do_not_mock_admin_mode, :request_store, :clean_gitlab_redis_shared_state do + let(:user) { create(:user) } + let(:user_to_delete) { create(:user) } + + before do + add_sidekiq_middleware + + sign_in(user) + end + + context 'as a regular user' do + it 'cannot delete user' do + visit admin_user_path(user_to_delete) + + expect(page).to have_gitlab_http_status(:not_found) + end + end + + context 'as an admin user' do + let(:user) { create(:admin) } + + context 'when admin mode disabled' do + it 'cannot delete user', :js do + visit admin_user_path(user_to_delete) + + expect(page).to have_content('Re-authentication required') + end + end + + context 'when admin mode enabled', :delete do + before do + gitlab_enable_admin_mode_sign_in(user) + end + + it 'can delete user', :js do + visit admin_user_path(user_to_delete) + click_button 'Delete user' + + page.within '.modal-dialog' do + find("input[name='username']").send_keys(user_to_delete.name) + click_button 'Delete user' + + wait_for_requests + end + + expect(page).to have_content('The user is being deleted.') + + # Perform jobs while logged out so that admin mode is only enabled in job metadata + execute_jobs_signed_out(user) + + visit admin_user_path(user_to_delete) + + expect(page).to have_title('Not Found') + end + end + end + + def add_sidekiq_middleware + Sidekiq::Testing.server_middleware do |chain| + chain.add Gitlab::SidekiqMiddleware::AdminMode::Server + end + end + + def execute_jobs_signed_out(user) + gitlab_sign_out + + Sidekiq::Worker.drain_all + + sign_in(user) + gitlab_enable_admin_mode_sign_in(user) + end +end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 3e8197588ed..954773e766d 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -2,46 +2,64 @@ require 'spec_helper' -describe 'Admin uses repository checks' do +describe 'Admin uses repository checks', :request_store, :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode do include StubENV + let(:admin) { create(:admin) } + before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') - sign_in(create(:admin)) + sign_in(admin) end - it 'to trigger a single check' do - project = create(:project) - visit_admin_project_page(project) + context 'when admin mode is disabled' do + it 'admin project page requires admin mode' do + project = create(:project) + visit_admin_project_page(project) - page.within('.repository-check') do - click_button 'Trigger repository check' + expect(page).not_to have_css('.repository-check') + expect(page).to have_content('Enter Admin Mode') end - - expect(page).to have_content('Repository check was triggered') end - it 'to see a single failed repository check', :js do - project = create(:project) - project.update_columns( - last_repository_check_failed: true, - last_repository_check_at: Time.now - ) - visit_admin_project_page(project) + context 'when admin mode is enabled' do + before do + gitlab_enable_admin_mode_sign_in(admin) + end + + it 'to trigger a single check', :js do + project = create(:project) + visit_admin_project_page(project) + + page.within('.repository-check') do + click_button 'Trigger repository check' + end - page.within('.alert') do - expect(page.text).to match(/Last repository check \(just now\) failed/) + expect(page).to have_content('Repository check was triggered') end - end - it 'to clear all repository checks', :js do - visit repository_admin_application_settings_path + it 'to see a single failed repository check', :js do + project = create(:project) + project.update_columns( + last_repository_check_failed: true, + last_repository_check_at: Time.now + ) + visit_admin_project_page(project) + + page.within('.alert') do + expect(page.text).to match(/Last repository check \(just now\) failed/) + end + end - expect(RepositoryCheck::ClearWorker).to receive(:perform_async) + it 'to clear all repository checks', :js do + visit repository_admin_application_settings_path - accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) - expect(page).to have_content('Started asynchronous removal of all repository check states.') + accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } + + expect(page).to have_content('Started asynchronous removal of all repository check states.') + end end def visit_admin_project_page(project) diff --git a/spec/features/projects/clusters/eks_spec.rb b/spec/features/projects/clusters/eks_spec.rb index bb0072fc8dd..a856376cb4b 100644 --- a/spec/features/projects/clusters/eks_spec.rb +++ b/spec/features/projects/clusters/eks_spec.rb @@ -30,6 +30,10 @@ describe 'AWS EKS Cluster', :js do it 'user sees a form to create an EKS cluster' do expect(page).to have_content('Create new cluster on EKS') end + + it 'highlights Amazon EKS logo' do + expect(page).to have_css('.js-create-aws-cluster-button.active') + end end end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 09cd1c6a765..0143461eadb 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -38,6 +38,10 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do click_link 'Google GKE' end + it 'highlights Google GKE logo' do + expect(page).to have_css('.js-create-gcp-cluster-button.active') + end + context 'when user filled form with valid parameters' do subject { submit_form } diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 3c82ae59cfa..daa987ea389 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -360,7 +360,7 @@ shared_examples 'Signup' do InvisibleCaptcha.timestamp_enabled = true stub_application_setting(recaptcha_enabled: true) allow_next_instance_of(RegistrationsController) do |instance| - allow(instance).to receive(:verify_recaptcha).and_return(false) + allow(instance).to receive(:verify_recaptcha).and_return(true) end end @@ -368,28 +368,53 @@ shared_examples 'Signup' do InvisibleCaptcha.timestamp_enabled = false end - it 'prevents from signing up' do - visit new_user_registration_path + context 'when reCAPTCHA detects malicious behaviour' do + before do + allow_next_instance_of(RegistrationsController) do |instance| + allow(instance).to receive(:verify_recaptcha).and_return(false) + end + end - fill_in 'new_user_username', with: new_user.username - fill_in 'new_user_email', with: new_user.email + it 'prevents from signing up' do + visit new_user_registration_path - if Gitlab::Experimentation.enabled?(:signup_flow) - fill_in 'new_user_first_name', with: new_user.first_name - fill_in 'new_user_last_name', with: new_user.last_name - else - fill_in 'new_user_name', with: new_user.name - fill_in 'new_user_email_confirmation', with: new_user.email + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email + + if Gitlab::Experimentation.enabled?(:signup_flow) + fill_in 'new_user_first_name', with: new_user.first_name + fill_in 'new_user_last_name', with: new_user.last_name + else + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_email_confirmation', with: new_user.email + end + + fill_in 'new_user_password', with: new_user.password + + expect { click_button 'Register' }.not_to change { User.count } + expect(page).to have_content('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') end + end - fill_in 'new_user_password', with: new_user.password + context 'when invisible captcha detects malicious behaviour' do + it 'prevents from signing up' do + visit new_user_registration_path - expect { click_button 'Register' }.not_to change { User.count } + fill_in 'new_user_username', with: new_user.username + fill_in 'new_user_email', with: new_user.email - if Gitlab::Experimentation.enabled?(:signup_flow) + if Gitlab::Experimentation.enabled?(:signup_flow) + fill_in 'new_user_first_name', with: new_user.first_name + fill_in 'new_user_last_name', with: new_user.last_name + else + fill_in 'new_user_name', with: new_user.name + fill_in 'new_user_email_confirmation', with: new_user.email + end + + fill_in 'new_user_password', with: new_user.password + + expect { click_button 'Register' }.not_to change { User.count } expect(page).to have_content('That was a bit too quick! Please resubmit.') - else - expect(page).to have_content('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') end end end diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js index d1ab152330e..d77690ffac0 100644 --- a/spec/frontend/jobs/store/mutations_spec.js +++ b/spec/frontend/jobs/store/mutations_spec.js @@ -157,17 +157,21 @@ describe('Jobs Store Mutations', () => { }); }); - describe('STOP_POLLING_TRACE', () => { - it('sets isTraceComplete to true', () => { - mutations[types.STOP_POLLING_TRACE](stateCopy); + describe('SET_TRACE_TIMEOUT', () => { + it('sets the traceTimeout id', () => { + const id = 7; - expect(stateCopy.isTraceComplete).toEqual(true); + expect(stateCopy.traceTimeout).not.toEqual(id); + + mutations[types.SET_TRACE_TIMEOUT](stateCopy, id); + + expect(stateCopy.traceTimeout).toEqual(id); }); }); - describe('RECEIVE_TRACE_ERROR', () => { - it('resets trace state and sets error to true', () => { - mutations[types.RECEIVE_TRACE_ERROR](stateCopy); + describe('STOP_POLLING_TRACE', () => { + it('sets isTraceComplete to true', () => { + mutations[types.STOP_POLLING_TRACE](stateCopy); expect(stateCopy.isTraceComplete).toEqual(true); }); diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js index eefb0313a0b..2b3e529b283 100644 --- a/spec/frontend/registry/settings/components/settings_form_spec.js +++ b/spec/frontend/registry/settings/components/settings_form_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import Tracking from '~/tracking'; import component from '~/registry/settings/components/settings_form.vue'; -import expirationPolicyForm from '~/registry/shared/components/expiration_policy_form.vue'; +import expirationPolicyFields from '~/registry/shared/components/expiration_policy_fields.vue'; import { createStore } from '~/registry/settings/store/'; import { UPDATE_SETTINGS_ERROR_MESSAGE, @@ -14,14 +14,34 @@ describe('Settings Form', () => { let store; let dispatchSpy; + const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; + const GlCard = { + name: 'gl-card-stub', + template: ` + <div> + <slot name="header"></slot> + <slot></slot> + <slot name="footer"></slot> + </div> + `, + }; + const trackingPayload = { label: 'docker_container_retention_and_expiration_policies', }; - const findForm = () => wrapper.find(expirationPolicyForm); + const findForm = () => wrapper.find({ ref: 'form-element' }); + const findFields = () => wrapper.find(expirationPolicyFields); + const findCancelButton = () => wrapper.find({ ref: 'cancel-button' }); + const findSaveButton = () => wrapper.find({ ref: 'save-button' }); + const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon); const mountComponent = () => { wrapper = shallowMount(component, { + stubs: { + GlCard, + GlLoadingIcon, + }, mocks: { $toast: { show: jest.fn(), @@ -47,46 +67,50 @@ describe('Settings Form', () => { let form; beforeEach(() => { form = findForm(); + dispatchSpy.mockReturnValue(); }); describe('data binding', () => { it('v-model change update the settings property', () => { - dispatchSpy.mockReturnValue(); - form.vm.$emit('input', 'foo'); + findFields().vm.$emit('input', 'foo'); expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' }); }); }); describe('form reset event', () => { + beforeEach(() => { + form.trigger('reset'); + }); it('calls the appropriate function', () => { - dispatchSpy.mockReturnValue(); - form.vm.$emit('reset'); expect(dispatchSpy).toHaveBeenCalledWith('resetSettings'); }); it('tracks the reset event', () => { - dispatchSpy.mockReturnValue(); - form.vm.$emit('reset'); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'reset_form', trackingPayload); }); }); describe('form submit event ', () => { + it('save has type submit', () => { + mountComponent(); + expect(findSaveButton().attributes('type')).toBe('submit'); + }); + it('dispatches the saveSettings action', () => { dispatchSpy.mockResolvedValue(); - form.vm.$emit('submit'); + form.trigger('submit'); expect(dispatchSpy).toHaveBeenCalledWith('saveSettings'); }); it('tracks the submit event', () => { dispatchSpy.mockResolvedValue(); - form.vm.$emit('submit'); + form.trigger('submit'); expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); }); it('show a success toast when submit succeed', () => { dispatchSpy.mockResolvedValue(); - form.vm.$emit('submit'); + form.trigger('submit'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success', @@ -96,7 +120,7 @@ describe('Settings Form', () => { it('show an error toast when submit fails', () => { dispatchSpy.mockRejectedValue(); - form.vm.$emit('submit'); + form.trigger('submit'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error', @@ -105,4 +129,52 @@ describe('Settings Form', () => { }); }); }); + + describe('form actions', () => { + describe('cancel button', () => { + beforeEach(() => { + store.commit('SET_SETTINGS', { foo: 'bar' }); + }); + + it('has type reset', () => { + expect(findCancelButton().attributes('type')).toBe('reset'); + }); + + it('is disabled when isEdited is false', () => + wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe('true'); + })); + + it('is disabled isLoading is true', () => { + store.commit('TOGGLE_LOADING'); + store.commit('UPDATE_SETTINGS', { settings: { foo: 'baz' } }); + return wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe('true'); + store.commit('TOGGLE_LOADING'); + }); + }); + + it('is enabled when isLoading is false and isEdited is true', () => { + store.commit('UPDATE_SETTINGS', { settings: { foo: 'baz' } }); + return wrapper.vm.$nextTick().then(() => { + expect(findCancelButton().attributes('disabled')).toBe(undefined); + }); + }); + }); + + describe('when isLoading is true', () => { + beforeEach(() => { + store.commit('TOGGLE_LOADING'); + }); + afterEach(() => { + store.commit('TOGGLE_LOADING'); + }); + + it('submit button is disabled and shows a spinner', () => { + const button = findSaveButton(); + expect(button.attributes('disabled')).toBeTruthy(); + expect(findLoadingIcon(button).exists()).toBe(true); + }); + }); + }); }); diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap new file mode 100644 index 00000000000..c5f5ea68d9e --- /dev/null +++ b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Expiration Policy Form renders 1`] = ` +<div + class="lh-2" +> + <glformgroup-stub + id="expiration-policy-toggle-group" + label="Expiration policy:" + label-align="right" + label-cols="3" + label-for="expiration-policy-toggle" + > + <div + class="d-flex align-items-start" + > + <gltoggle-stub + id="expiration-policy-toggle" + labeloff="Toggle Status: OFF" + labelon="Toggle Status: ON" + /> + + <span + class="mb-2 ml-1 lh-2" + > + Docker tag expiration policy is + <strong> + disabled + </strong> + </span> + </div> + </glformgroup-stub> + + <glformgroup-stub + id="expiration-policy-interval-group" + label="Expiration interval:" + label-align="right" + label-cols="3" + label-for="expiration-policy-interval" + > + <glformselect-stub + disabled="true" + id="expiration-policy-interval" + > + <option + value="foo" + > + + Foo + + </option> + <option + value="bar" + > + + Bar + + </option> + </glformselect-stub> + </glformgroup-stub> + <glformgroup-stub + id="expiration-policy-schedule-group" + label="Expiration schedule:" + label-align="right" + label-cols="3" + label-for="expiration-policy-schedule" + > + <glformselect-stub + disabled="true" + id="expiration-policy-schedule" + > + <option + value="foo" + > + + Foo + + </option> + <option + value="bar" + > + + Bar + + </option> + </glformselect-stub> + </glformgroup-stub> + <glformgroup-stub + id="expiration-policy-latest-group" + label="Number of tags to retain:" + label-align="right" + label-cols="3" + label-for="expiration-policy-latest" + > + <glformselect-stub + disabled="true" + id="expiration-policy-latest" + > + <option + value="foo" + > + + Foo + + </option> + <option + value="bar" + > + + Bar + + </option> + </glformselect-stub> + </glformgroup-stub> + + <glformgroup-stub + id="expiration-policy-name-matching-group" + invalid-feedback="The value of this input should be less than 255 characters" + label="Docker tags with names matching this regex pattern will expire:" + label-align="right" + label-cols="3" + label-for="expiration-policy-name-matching" + > + <glformtextarea-stub + disabled="true" + id="expiration-policy-name-matching" + placeholder=".*" + trim="" + value="" + /> + </glformgroup-stub> +</div> +`; diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_form_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_form_spec.js.snap deleted file mode 100644 index b53736951e1..00000000000 --- a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_form_spec.js.snap +++ /dev/null @@ -1,186 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Expiration Policy Form renders 1`] = ` -<form - class="lh-2" -> - <div - class="card" - > - <!----> - <div - class="card-header" - > - - Tag expiration policy - - </div> - <div - class="card-body" - > - <!----> - <!----> - - <glformgroup-stub - id="expiration-policy-toggle-group" - label="Expiration policy:" - label-align="right" - label-cols="3" - label-for="expiration-policy-toggle" - > - <div - class="d-flex align-items-start" - > - <gltoggle-stub - id="expiration-policy-toggle" - labeloff="Toggle Status: OFF" - labelon="Toggle Status: ON" - /> - - <span - class="mb-2 ml-1 lh-2" - > - Docker tag expiration policy is - <strong> - disabled - </strong> - </span> - </div> - </glformgroup-stub> - - <glformgroup-stub - id="expiration-policy-interval-group" - label="Expiration interval:" - label-align="right" - label-cols="3" - label-for="expiration-policy-interval" - > - <glformselect-stub - disabled="true" - id="expiration-policy-interval" - > - <option - value="foo" - > - - Foo - - </option> - <option - value="bar" - > - - Bar - - </option> - </glformselect-stub> - </glformgroup-stub> - - <glformgroup-stub - id="expiration-policy-schedule-group" - label="Expiration schedule:" - label-align="right" - label-cols="3" - label-for="expiration-policy-schedule" - > - <glformselect-stub - disabled="true" - id="expiration-policy-schedule" - > - <option - value="foo" - > - - Foo - - </option> - <option - value="bar" - > - - Bar - - </option> - </glformselect-stub> - </glformgroup-stub> - - <glformgroup-stub - id="expiration-policy-latest-group" - label="Number of tags to retain:" - label-align="right" - label-cols="3" - label-for="expiration-policy-latest" - > - <glformselect-stub - disabled="true" - id="expiration-policy-latest" - > - <option - value="foo" - > - - Foo - - </option> - <option - value="bar" - > - - Bar - - </option> - </glformselect-stub> - </glformgroup-stub> - - <glformgroup-stub - id="expiration-policy-name-matching-group" - invalid-feedback="The value of this input should be less than 255 characters" - label="Docker tags with names matching this regex pattern will expire:" - label-align="right" - label-cols="3" - label-for="expiration-policy-name-matching" - > - <glformtextarea-stub - disabled="true" - id="expiration-policy-name-matching" - placeholder=".*" - trim="" - value="" - /> - </glformgroup-stub> - - </div> - <div - class="card-footer" - > - <div - class="d-flex justify-content-end" - > - <glbutton-stub - class="mr-2 d-block" - size="md" - type="reset" - variant="secondary" - > - - Cancel - - </glbutton-stub> - - <glbutton-stub - class="d-flex justify-content-center align-items-center js-no-auto-disable" - size="md" - type="submit" - variant="success" - > - - Save expiration policy - - <!----> - </glbutton-stub> - </div> - </div> - <!----> - </div> -</form> -`; diff --git a/spec/frontend/registry/shared/components/expiration_policy_form_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js index b51519925f1..b384fd62406 100644 --- a/spec/frontend/registry/shared/components/expiration_policy_form_spec.js +++ b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import stubChildren from 'helpers/stub_children'; -import component from '~/registry/shared/components/expiration_policy_form.vue'; +import component from '~/registry/shared/components/expiration_policy_fields.vue'; import { NAME_REGEX_LENGTH } from '~/registry/shared/constants'; import { formOptions } from '../mock_data'; @@ -10,22 +10,14 @@ describe('Expiration Policy Form', () => { const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy'; - const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; - const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`); const findFormElements = (name, parent = wrapper) => parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`); - const findCancelButton = () => wrapper.find({ ref: 'cancel-button' }); - const findSaveButton = () => wrapper.find({ ref: 'save-button' }); - const findForm = () => wrapper.find({ ref: 'form-element' }); - const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon); const mountComponent = props => { wrapper = mount(component, { stubs: { ...stubChildren(component), - GlCard: false, - GlLoadingIcon, }, propsData: { formOptions, @@ -114,77 +106,20 @@ describe('Expiration Policy Form', () => { }, ); - describe('form actions', () => { - describe('cancel button', () => { - it('has type reset', () => { - mountComponent(); - expect(findCancelButton().attributes('type')).toBe('reset'); - }); - - it('is disabled when disableCancelButton is true', () => { - mountComponent({ disableCancelButton: true }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - }); - }); - - it('is disabled isLoading is true', () => { - mountComponent({ isLoading: true }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - }); - }); - - it('is enabled when isLoading and disableCancelButton are false', () => { - mountComponent({ disableCancelButton: false, isLoading: false }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe(undefined); - }); - }); - }); - - describe('form cancel event', () => { - it('calls the appropriate function', () => { - mountComponent(); - findForm().trigger('reset'); - expect(wrapper.emitted('reset')).toBeTruthy(); - }); - }); - - it('save has type submit', () => { - mountComponent(); - expect(findSaveButton().attributes('type')).toBe('submit'); - }); - - describe('when isLoading is true', () => { - beforeEach(() => { - mountComponent({ isLoading: true }); - }); - - it.each` - elementName - ${'toggle'} - ${'interval'} - ${'schedule'} - ${'latest'} - ${'name-matching'} - `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => { - expect(findFormElements(elementName).attributes('disabled')).toBe('true'); - }); - - it('submit button is disabled and shows a spinner', () => { - const button = findSaveButton(); - expect(button.attributes('disabled')).toBeTruthy(); - expect(findLoadingIcon(button)).toExist(); - }); + describe('when isLoading is true', () => { + beforeEach(() => { + mountComponent({ isLoading: true }); }); - describe('form submit event ', () => { - it('calls the appropriate function', () => { - mountComponent(); - findForm().trigger('submit'); - expect(wrapper.emitted('submit')).toBeTruthy(); - }); + it.each` + elementName + ${'toggle'} + ${'interval'} + ${'schedule'} + ${'latest'} + ${'name-matching'} + `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => { + expect(findFormElements(elementName).attributes('disabled')).toBe('true'); }); }); @@ -196,20 +131,20 @@ describe('Expiration Policy Form', () => { mountComponent({ value: { name_regex: invalidString } }); }); - it('save btn is disabled', () => { - expect(findSaveButton().attributes('disabled')).toBeTruthy(); - }); - it('nameRegexState is false', () => { expect(wrapper.vm.nameRegexState).toBe(false); }); + + it('emit the @invalidated event', () => { + expect(wrapper.emitted('invalidated')).toBeTruthy(); + }); }); it('if the user did not type validation is null', () => { mountComponent({ value: { name_regex: '' } }); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.nameRegexState).toBe(null); - expect(findSaveButton().attributes('disabled')).toBeFalsy(); + expect(wrapper.emitted('validated')).toBeTruthy(); }); }); diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb index ff8394b9475..5651b899ed0 100644 --- a/spec/helpers/clusters_helper_spec.rb +++ b/spec/helpers/clusters_helper_spec.rb @@ -58,32 +58,4 @@ describe ClustersHelper do it { is_expected.to eq('Create new cluster') } end end - - describe '#render_new_provider_form' do - subject { helper.new_cluster_partial(provider: provider) } - - context 'GCP provider' do - let(:provider) { 'gcp' } - - it { is_expected.to eq('clusters/clusters/gcp/new') } - end - - context 'AWS provider' do - let(:provider) { 'aws' } - - it { is_expected.to eq('clusters/clusters/aws/new') } - end - - context 'other provider' do - let(:provider) { 'other' } - - it { is_expected.to eq('clusters/clusters/cloud_providers/cloud_provider_selector') } - end - - context 'no provider' do - let(:provider) { nil } - - it { is_expected.to eq('clusters/clusters/cloud_providers/cloud_provider_selector') } - end - end end diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index 0fcd6080106..31b49c45908 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -6,7 +6,6 @@ import axios from '~/lib/utils/axios_utils'; import jobApp from '~/jobs/components/job_app.vue'; import createStore from '~/jobs/store'; import * as types from '~/jobs/store/mutation_types'; -import { resetStore } from '../store/helpers'; import job from '../mock_data'; describe('Job App ', () => { @@ -16,24 +15,29 @@ describe('Job App ', () => { let vm; let mock; - const props = { + const initSettings = { endpoint: `${gl.TEST_HOST}jobs/123.json`, + pagePath: `${gl.TEST_HOST}jobs/123`, + logState: + 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', + }; + + const props = { runnerHelpUrl: 'help/runner', deploymentHelpUrl: 'help/deployment', runnerSettingsUrl: 'settings/ci-cd/runners', variablesSettingsUrl: 'settings/ci-cd/variables', terminalPath: 'jobs/123/terminal', - pagePath: `${gl.TEST_HOST}jobs/123`, projectPath: 'user-name/project-name', subscriptionsMoreMinutesUrl: 'https://customers.gitlab.com/buy_pipeline_minutes', - logState: - 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', }; const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS); const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => { - mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData }); - mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData); + mock.onGet(initSettings.endpoint).replyOnce(200, { ...job, ...jobData }); + mock.onGet(`${initSettings.pagePath}/trace.json`).reply(200, traceData); + + store.dispatch('init', initSettings); vm = mountComponentWithStore(Component, { props, store }); @@ -46,7 +50,6 @@ describe('Job App ', () => { }); afterEach(() => { - resetStore(store); vm.$destroy(); mock.restore(); }); @@ -384,7 +387,6 @@ describe('Job App ', () => { }) .then(done) .catch(done.fail); - done(); }); it('displays remaining time for a delayed job', done => { diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js index c0e8dbf9b22..47257688bd5 100644 --- a/spec/javascripts/jobs/store/actions_spec.js +++ b/spec/javascripts/jobs/store/actions_spec.js @@ -15,6 +15,7 @@ import { scrollBottom, requestTrace, fetchTrace, + startPollingTrace, stopPollingTrace, receiveTraceSuccess, receiveTraceError, @@ -241,6 +242,50 @@ describe('Job State actions', () => { done, ); }); + + describe('when job is incomplete', () => { + let tracePayload; + + beforeEach(() => { + tracePayload = { + html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', + complete: false, + }; + + mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(200, tracePayload); + }); + + it('dispatches startPollingTrace', done => { + testAction( + fetchTrace, + null, + mockedState, + [], + [ + { type: 'toggleScrollisInBottom', payload: true }, + { type: 'receiveTraceSuccess', payload: tracePayload }, + { type: 'startPollingTrace' }, + ], + done, + ); + }); + + it('does not dispatch startPollingTrace when timeout is non-empty', done => { + mockedState.traceTimeout = 1; + + testAction( + fetchTrace, + null, + mockedState, + [], + [ + { type: 'toggleScrollisInBottom', payload: true }, + { type: 'receiveTraceSuccess', payload: tracePayload }, + ], + done, + ); + }); + }); }); describe('error', () => { @@ -265,16 +310,69 @@ describe('Job State actions', () => { }); }); + describe('startPollingTrace', () => { + let dispatch; + let commit; + + beforeEach(() => { + jasmine.clock().install(); + + dispatch = jasmine.createSpy(); + commit = jasmine.createSpy(); + + startPollingTrace({ dispatch, commit }); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should save the timeout id but not call fetchTrace', () => { + expect(commit).toHaveBeenCalledWith(types.SET_TRACE_TIMEOUT, 1); + expect(dispatch).not.toHaveBeenCalledWith('fetchTrace'); + }); + + describe('after timeout has passed', () => { + beforeEach(() => { + jasmine.clock().tick(4000); + }); + + it('should clear the timeout id and fetchTrace', () => { + expect(commit).toHaveBeenCalledWith(types.SET_TRACE_TIMEOUT, 0); + expect(dispatch).toHaveBeenCalledWith('fetchTrace'); + }); + }); + }); + describe('stopPollingTrace', () => { + let origTimeout; + + beforeEach(() => { + // Can't use spyOn(window, 'clearTimeout') because this caused unrelated specs to timeout + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23838#note_280277727 + origTimeout = window.clearTimeout; + window.clearTimeout = jasmine.createSpy(); + }); + + afterEach(() => { + window.clearTimeout = origTimeout; + }); + it('should commit STOP_POLLING_TRACE mutation ', done => { + const traceTimeout = 7; + testAction( stopPollingTrace, null, - mockedState, - [{ type: types.STOP_POLLING_TRACE }], + { ...mockedState, traceTimeout }, + [{ type: types.SET_TRACE_TIMEOUT, payload: 0 }, { type: types.STOP_POLLING_TRACE }], [], - done, - ); + ) + .then(() => { + expect(window.clearTimeout).toHaveBeenCalledWith(traceTimeout); + }) + .then(done) + .catch(done.fail); }); }); @@ -292,15 +390,8 @@ describe('Job State actions', () => { }); describe('receiveTraceError', () => { - it('should commit RECEIVE_TRACE_ERROR mutation ', done => { - testAction( - receiveTraceError, - null, - mockedState, - [{ type: types.RECEIVE_TRACE_ERROR }], - [], - done, - ); + it('should commit stop polling trace', done => { + testAction(receiveTraceError, null, mockedState, [], [{ type: 'stopPollingTrace' }], done); }); }); diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb index 3b3db0f7315..7c2fdac6c25 100644 --- a/spec/lib/gitlab/auth/current_user_mode_spec.rb +++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do +describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do include_context 'custom session' - let(:user) { build(:user) } + let(:user) { build_stubbed(:user) } subject { described_class.new(user) } @@ -13,54 +13,66 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session]) end - describe '#admin_mode?', :request_store do - context 'when the user is a regular user' do - it 'is false by default' do - expect(subject.admin_mode?).to be(false) - end + shared_examples 'admin mode cannot be enabled' do + it 'is false by default' do + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password) + it 'cannot be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(password: nil) + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(password: nil) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'cannot be enabled with empty params' do - subject.enable_admin_mode! + it 'cannot be enabled with empty params' do + subject.enable_admin_mode! - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end - it 'disable has no effect' do - subject.enable_admin_mode! - subject.disable_admin_mode! + it 'disable has no effect' do + subject.enable_admin_mode! + subject.disable_admin_mode! + + expect(subject.admin_mode?).to be(false) + end + + context 'skipping password validation' do + it 'cannot be enabled with a valid password' do + subject.enable_admin_mode!(password: user.password, skip_password_validation: true) expect(subject.admin_mode?).to be(false) end - context 'skipping password validation' do - it 'cannot be enabled with a valid password' do - subject.enable_admin_mode!(password: user.password, skip_password_validation: true) + it 'cannot be enabled with an invalid password' do + subject.enable_admin_mode!(skip_password_validation: true) - expect(subject.admin_mode?).to be(false) - end + expect(subject.admin_mode?).to be(false) + end + end + end - it 'cannot be enabled with an invalid password' do - subject.enable_admin_mode!(skip_password_validation: true) + describe '#admin_mode?' do + context 'when the user is a regular user' do + it_behaves_like 'admin mode cannot be enabled' - expect(subject.admin_mode?).to be(false) + context 'bypassing session' do + it_behaves_like 'admin mode cannot be enabled' do + around do |example| + described_class.bypass_session!(user.id) { example.run } + end end end end context 'when the user is an admin' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } context 'when admin mode not requested' do it 'is false by default' do @@ -148,11 +160,36 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end end end + + context 'bypassing session' do + it 'is active by default' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(true) + end + end + + it 'enable has no effect' do + described_class.bypass_session!(user.id) do + subject.request_admin_mode! + subject.enable_admin_mode!(password: user.password) + + expect(subject.admin_mode?).to be(true) + end + end + + it 'disable has no effect' do + described_class.bypass_session!(user.id) do + subject.disable_admin_mode! + + expect(subject.admin_mode?).to be(true) + end + end + end end end describe '#enable_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'creates a timestamp in the session' do subject.request_admin_mode! @@ -163,7 +200,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end describe '#enable_sessionless_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'enabled admin mode without password' do subject.enable_sessionless_admin_mode! @@ -173,7 +210,7 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end describe '#disable_admin_mode!' do - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } it 'sets the session timestamp to nil' do subject.request_admin_mode! @@ -183,6 +220,73 @@ describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode do end end + describe '.bypass_session!' do + context 'with a regular user' do + it 'admin mode is false' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(false) + expect(described_class.bypass_session_admin_id).to be(user.id) + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'with an admin user' do + let(:user) { build_stubbed(:user, :admin) } + + it 'admin mode is true' do + described_class.bypass_session!(user.id) do + expect(subject.admin_mode?).to be(true) + expect(described_class.bypass_session_admin_id).to be(user.id) + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + end + + describe '.with_current_request_admin_mode' do + context 'with a regular user' do + it 'user is not available inside nor outside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be_nil + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'with an admin user' do + let(:user) { build_stubbed(:user, :admin) } + + context 'admin mode is disabled' do + it 'user is not available inside nor outside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be_nil + end + + expect(described_class.bypass_session_admin_id).to be_nil + end + end + + context 'admin mode is enabled' do + before do + subject.request_admin_mode! + subject.enable_admin_mode!(password: user.password) + end + + it 'user is available only inside the yielded block' do + described_class.with_current_admin(user) do + expect(described_class.current_admin).to be(user) + end + + expect(described_class.current_admin).to be_nil + end + end + end + end + def expected_session_entry(value_matcher) { Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including( diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb index 5cad479ff05..4714712f733 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migr let(:group) { namespaces.create!(name: 'foo', path: 'foo') } let(:subgroup) { namespaces.create!(name: 'bar', path: 'bar', parent_id: group.id) } - describe described_class::Storage::HashedProject do + describe described_class::Storage::Hashed do let(:project) { double(id: 555) } subject(:project_storage) { described_class.new(project) } diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb new file mode 100644 index 00000000000..f6449bae8c3 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::AdminMode::Client, :do_not_mock_admin_mode, :request_store do + include AdminModeHelper + + let(:worker) do + Class.new do + def perform; end + end + end + + let(:job) { {} } + let(:queue) { :test } + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, nil, &b) + end.to yield_control.once + end + + context 'user is a regular user' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'user is an administrator' do + let(:admin) { create(:admin) } + + context 'admin mode disabled' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'admin mode enabled' do + before do + enable_admin_mode!(admin) + end + + context 'when sidekiq required context not set' do + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end + + context 'when user stored in current request' do + it 'has admin mode field in payload' do + Gitlab::Auth::CurrentUserMode.with_current_admin(admin) do + subject.call(worker, job, queue, nil) { nil } + + expect(job).to include('admin_mode_user_id' => admin.id) + end + end + end + + context 'when bypassing session' do + it 'has admin mode field in payload' do + Gitlab::Auth::CurrentUserMode.bypass_session!(admin.id) do + subject.call(worker, job, queue, nil) { nil } + + expect(job).to include('admin_mode_user_id' => admin.id) + end + end + end + end + end + + context 'admin mode feature disabled' do + before do + stub_feature_flags(user_mode_in_session: false) + end + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, nil, &b) + end.to yield_control.once + end + + it 'no admin mode field in payload' do + subject.call(worker, job, queue, nil) { nil } + + expect(job).not_to include('admin_mode_user_id') + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb new file mode 100644 index 00000000000..60475f0e403 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::AdminMode::Server, :do_not_mock_admin_mode, :request_store do + include AdminModeHelper + + let(:worker) do + Class.new do + def perform; end + end + end + + let(:job) { {} } + let(:queue) { :test } + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, &b) + end.to yield_control.once + end + + context 'job has no admin mode field' do + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end + + context 'job has admin mode field' do + let(:admin) { create(:admin) } + + context 'nil admin mode id' do + let(:job) { { 'admin_mode_user_id' => nil } } + + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end + + context 'valid admin mode id' do + let(:job) { { 'admin_mode_user_id' => admin.id } } + + it 'session is bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be(admin.id) + end + end + end + end + + context 'admin mode feature disabled' do + before do + stub_feature_flags(user_mode_in_session: false) + end + + it 'yields block' do + expect do |b| + subject.call(worker, job, queue, &b) + end.to yield_control.once + end + + it 'session is not bypassed' do + subject.call(worker, job, queue) do + expect(Gitlab::Auth::CurrentUserMode.bypass_session_admin_id).to be_nil + end + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index e8dcbbd2ee1..19242d25e27 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -45,7 +45,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::ArgumentsLogger, Gitlab::SidekiqMiddleware::MemoryKiller, Gitlab::SidekiqMiddleware::RequestStoreMiddleware, - Gitlab::SidekiqMiddleware::WorkerContext::Server + Gitlab::SidekiqMiddleware::WorkerContext::Server, + Gitlab::SidekiqMiddleware::AdminMode::Server ] end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } @@ -115,7 +116,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqStatus::ClientMiddleware, Gitlab::SidekiqMiddleware::ClientMetrics, Gitlab::SidekiqMiddleware::WorkerContext::Client, - Labkit::Middleware::Sidekiq::Client + Labkit::Middleware::Sidekiq::Client, + Gitlab::SidekiqMiddleware::AdminMode::Client ] end diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb index 64ab8d85807..25538db159e 100644 --- a/spec/lib/microsoft_teams/notifier_spec.rb +++ b/spec/lib/microsoft_teams/notifier_spec.rb @@ -17,7 +17,7 @@ describe MicrosoftTeams::Notifier do text: '[#1 Awesome issue](http://localhost/namespace2/gitlabhq/issues/1)', image: 'http://someimage.com' }, - attachments: 'please fix' + attachments: "[GitLab](https://gitlab.com)\n\n- _Ruby_\n- **Go**\n" } end @@ -31,13 +31,7 @@ describe MicrosoftTeams::Notifier do 'activityImage' => 'http://someimage.com' }, { - 'title' => 'Details', - 'facts' => [ - { - 'name' => 'Attachments', - 'value' => 'please fix' - } - ] + text: "[GitLab](https://gitlab.com)\n\n- _Ruby_\n- **Go**\n" } ], 'title' => 'JohnDoe4/project2', @@ -54,4 +48,14 @@ describe MicrosoftTeams::Notifier do expect(subject.ping(options)).to be true end end + + describe '#body' do + it 'returns Markdown-based body when HTML was passed' do + expect(subject.send(:body, options)).to eq(body.to_json) + end + + it 'fails when empty Hash was passed' do + expect { subject.send(:body, {}) }.to raise_error(ArgumentError) + end + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5c3f7c09e22..5aa74134692 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -84,6 +84,16 @@ describe Issue do end end + describe '.simple_sorts' do + it 'includes all keys' do + expect(described_class.simple_sorts.keys).to include( + *%w(created_asc created_at_asc created_date created_desc created_at_desc + closest_future_date closest_future_date_asc due_date due_date_asc due_date_desc + id_asc id_desc relative_position relative_position_asc + updated_desc updated_asc updated_at_asc updated_at_desc)) + end + end + describe '#order_by_position_and_priority' do let(:project) { create :project } let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 438ed6d83fa..42151d86ac0 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2985,9 +2985,9 @@ describe User, :do_not_mock_admin_mode do end end - describe '#can_read_all_resources?' do + describe '#can_read_all_resources?', :request_store do it 'returns false for regular user' do - user = build(:user) + user = build_stubbed(:user) expect(user.can_read_all_resources?).to be_falsy end @@ -2995,7 +2995,7 @@ describe User, :do_not_mock_admin_mode do context 'for admin user' do include_context 'custom session' - let(:user) { build(:user, :admin) } + let(:user) { build_stubbed(:user, :admin) } context 'when admin mode is disabled' do it 'returns false' do diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb index 81aee4cfcac..ae5af9e0f29 100644 --- a/spec/policies/base_policy_spec.rb +++ b/spec/policies/base_policy_spec.rb @@ -23,8 +23,8 @@ describe BasePolicy, :do_not_mock_admin_mode do end describe 'read cross project' do - let(:current_user) { create(:user) } - let(:user) { create(:user) } + let(:current_user) { build_stubbed(:user) } + let(:user) { build_stubbed(:user) } subject { described_class.new(current_user, [user]) } @@ -38,7 +38,7 @@ describe BasePolicy, :do_not_mock_admin_mode do it { is_expected.not_to be_allowed(:read_cross_project) } context 'for admins' do - let(:current_user) { build(:admin) } + let(:current_user) { build_stubbed(:admin) } subject { described_class.new(current_user, nil) } @@ -56,14 +56,14 @@ describe BasePolicy, :do_not_mock_admin_mode do end describe 'full private access' do - let(:current_user) { create(:user) } + let(:current_user) { build_stubbed(:user) } subject { described_class.new(current_user, nil) } it { is_expected.not_to be_allowed(:read_all_resources) } context 'for admins' do - let(:current_user) { build(:admin) } + let(:current_user) { build_stubbed(:admin) } it 'allowed when in admin mode' do enable_admin_mode!(current_user) diff --git a/spec/services/error_tracking/issue_details_service_spec.rb b/spec/services/error_tracking/issue_details_service_spec.rb index 60d6172ef64..9f217deda21 100644 --- a/spec/services/error_tracking/issue_details_service_spec.rb +++ b/spec/services/error_tracking/issue_details_service_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' describe ErrorTracking::IssueDetailsService do include_context 'sentry error tracking context' + subject { described_class.new(project, user, params) } + describe '#execute' do context 'with authorized user' do context 'when issue_details returns a detailed error' do diff --git a/spec/services/error_tracking/issue_latest_event_service_spec.rb b/spec/services/error_tracking/issue_latest_event_service_spec.rb index 7f53eabd717..078d7511850 100644 --- a/spec/services/error_tracking/issue_latest_event_service_spec.rb +++ b/spec/services/error_tracking/issue_latest_event_service_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' describe ErrorTracking::IssueLatestEventService do include_context 'sentry error tracking context' + subject { described_class.new(project, user) } + describe '#execute' do context 'with authorized user' do context 'when issue_latest_event returns an error event' do diff --git a/spec/services/error_tracking/issue_update_service_spec.rb b/spec/services/error_tracking/issue_update_service_spec.rb index ad1dafe6ccc..78388328a22 100644 --- a/spec/services/error_tracking/issue_update_service_spec.rb +++ b/spec/services/error_tracking/issue_update_service_spec.rb @@ -7,16 +7,19 @@ describe ErrorTracking::IssueUpdateService do let(:arguments) { { issue_id: 1234, status: 'resolved' } } - subject { described_class.new(project, user, arguments) } + subject(:update_service) { described_class.new(project, user, arguments) } shared_examples 'does not perform close issue flow' do it 'does not call the close issue service' do + update_service.execute + expect(issue_close_service) - .not_to receive(:execute) + .not_to have_received(:execute) end it 'does not create system note' do expect(SystemNoteService).not_to receive(:close_after_error_tracking_resolve) + update_service.execute end end @@ -31,13 +34,13 @@ describe ErrorTracking::IssueUpdateService do end it 'returns the response' do - expect(result).to eq(update_issue_response.merge(status: :success, closed_issue_iid: nil)) + expect(update_service.execute).to eq(update_issue_response.merge(status: :success, closed_issue_iid: nil)) end it 'updates any related issue' do - expect(subject).to receive(:update_related_issue) + expect(update_service).to receive(:update_related_issue) - result + update_service.execute end context 'related issue and resolving' do @@ -48,39 +51,46 @@ describe ErrorTracking::IssueUpdateService do let(:issue_close_service) { spy(:issue_close_service) } before do - allow_any_instance_of(SentryIssueFinder) - .to receive(:execute) - .and_return(sentry_issue) + allow_next_instance_of(SentryIssueFinder) do |finder| + allow(finder).to receive(:execute).and_return(sentry_issue) + end allow(Issues::CloseService) .to receive(:new) .and_return(issue_close_service) - end - after do - result + allow(issue_close_service) + .to receive(:execute) + .and_return(issue) end it 'closes the issue' do + update_service.execute + expect(issue_close_service) - .to receive(:execute) + .to have_received(:execute) .with(issue, system_note: false) - .and_return(issue) end - it 'creates a system note' do - expect(SystemNoteService).to receive(:close_after_error_tracking_resolve) - end + context 'issues gets closed' do + let(:closed_issue) { create(:issue, :closed, project: project) } - it 'returns a response with closed issue' do - closed_issue = create(:issue, :closed, project: project) + before do + expect(issue_close_service) + .to receive(:execute) + .with(issue, system_note: false) + .and_return(closed_issue) + end - expect(issue_close_service) - .to receive(:execute) - .with(issue, system_note: false) - .and_return(closed_issue) + it 'creates a system note' do + expect(SystemNoteService).to receive(:close_after_error_tracking_resolve) + + update_service.execute + end - expect(result).to eq(status: :success, updated: true, closed_issue_iid: closed_issue.iid) + it 'returns a response with closed issue' do + expect(update_service.execute).to eq(status: :success, updated: true, closed_issue_iid: closed_issue.iid) + end end context 'issue is already closed' do diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index bf637b70aaf..b81dd3d7e3f 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Projects::AfterRenameService do let(:rugged_config) { rugged_repo(project.repository).config } let(:legacy_storage) { Storage::LegacyProject.new(project) } - let(:hashed_storage) { Storage::HashedProject.new(project) } + let(:hashed_storage) { Storage::Hashed.new(project) } let!(:path_before_rename) { project.path } let!(:full_path_before_rename) { project.full_path } let!(:path_after_rename) { "#{project.path}-renamed" } diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index ab9d2bdba8f..b0827f6a2ee 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -7,7 +7,7 @@ describe Projects::HashedStorage::MigrateAttachmentsService do let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } let(:legacy_storage) { Storage::LegacyProject.new(project) } - let(:hashed_storage) { Storage::HashedProject.new(project) } + let(:hashed_storage) { Storage::Hashed.new(project) } let!(:upload) { Upload.find_by(path: file_uploader.upload_path) } let(:file_uploader) { build(:file_uploader, project: project) } diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 132b895fc35..71be335c11d 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -8,7 +8,7 @@ describe Projects::HashedStorage::MigrateRepositoryService do let(:gitlab_shell) { Gitlab::Shell.new } let(:project) { create(:project, :legacy_storage, :repository, :wiki_repo) } let(:legacy_storage) { Storage::LegacyProject.new(project) } - let(:hashed_storage) { Storage::HashedProject.new(project) } + let(:hashed_storage) { Storage::Hashed.new(project) } subject(:service) { described_class.new(project: project, old_disk_path: project.disk_path) } diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb index c2ba9626f41..98b343371df 100644 --- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -7,7 +7,7 @@ describe Projects::HashedStorage::RollbackAttachmentsService do let(:project) { create(:project, :repository, skip_disk_validation: true) } let(:legacy_storage) { Storage::LegacyProject.new(project) } - let(:hashed_storage) { Storage::HashedProject.new(project) } + let(:hashed_storage) { Storage::Hashed.new(project) } let!(:upload) { Upload.find_by(path: file_uploader.upload_path) } let(:file_uploader) { build(:file_uploader, project: project) } diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index 97c7c0af946..6dcd2ff4555 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -8,7 +8,7 @@ describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis let(:gitlab_shell) { Gitlab::Shell.new } let(:project) { create(:project, :repository, :wiki_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) } let(:legacy_storage) { Storage::LegacyProject.new(project) } - let(:hashed_storage) { Storage::HashedProject.new(project) } + let(:hashed_storage) { Storage::Hashed.new(project) } subject(:service) { described_class.new(project: project, old_disk_path: project.disk_path) } diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb index ee678580fb9..f06de53f0c1 100644 --- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb +++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb @@ -9,8 +9,6 @@ shared_context 'sentry error tracking context' do let(:params) { {} } let(:result) { subject.execute } - subject { described_class.new(project, user, params) } - let(:error_tracking_setting) do create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project) end |