diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-29 15:09:39 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-29 15:09:39 +0000 |
commit | 58320d8e039f4edc8e8000d98f077017b6c78b61 (patch) | |
tree | ce94f463703981b7103e762b60105aff59ee27e9 /spec | |
parent | 6f9f4f0580bf5a78af4c5da067d4acf43094dc98 (diff) | |
download | gitlab-ce-58320d8e039f4edc8e8000d98f077017b6c78b61.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
15 files changed, 548 insertions, 151 deletions
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index a5a3dc463d3..0775903cff6 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -15,10 +15,7 @@ RSpec.describe Import::GithubController do it "redirects to GitHub for an access token if logged in with GitHub" do allow(controller).to receive(:logged_in_with_provider?).and_return(true) expect(controller).to receive(:go_to_provider_for_permissions).and_call_original - allow_any_instance_of(Gitlab::LegacyGithubImport::Client) - .to receive(:authorize_url) - .with(users_import_github_callback_url) - .and_call_original + allow(controller).to receive(:authorize_url).with(users_import_github_callback_url).and_call_original get :new @@ -46,13 +43,15 @@ RSpec.describe Import::GithubController do end describe "GET callback" do + before do + allow(controller).to receive(:get_token).and_return(token) + allow(controller).to receive(:oauth_options).and_return({}) + + stub_omniauth_provider('github') + end + it "updates access token" do token = "asdasd12345" - allow_any_instance_of(Gitlab::LegacyGithubImport::Client) - .to receive(:get_token).and_return(token) - allow_any_instance_of(Gitlab::LegacyGithubImport::Client) - .to receive(:github_options).and_return({}) - stub_omniauth_provider('github') get :callback @@ -67,6 +66,54 @@ RSpec.describe Import::GithubController do describe "GET status" do it_behaves_like 'a GitHub-ish import controller: GET status' + + context 'when using OAuth' do + before do + allow(controller).to receive(:logged_in_with_provider?).and_return(true) + end + + context 'when OAuth config is missing' do + let(:new_import_url) { public_send("new_import_#{provider}_url") } + + before do + allow(controller).to receive(:oauth_config).and_return(nil) + end + + it 'returns missing config error' do + expect(controller).to receive(:go_to_provider_for_permissions).and_call_original + + get :status + + expect(session[:"#{provider}_access_token"]).to be_nil + expect(controller).to redirect_to(new_import_url) + expect(flash[:alert]).to eq('OAuth configuration for GitHub missing.') + end + end + end + + context 'when feature remove_legacy_github_client is disabled' do + before do + stub_feature_flags(remove_legacy_github_client: false) + end + + it 'uses Gitlab::LegacyGitHubImport::Client' do + expect(controller.send(:client)).to be_instance_of(Gitlab::LegacyGithubImport::Client) + + get :status + end + end + + context 'when feature remove_legacy_github_client is enabled' do + before do + stub_feature_flags(remove_legacy_github_client: true) + end + + it 'uses Gitlab::GithubImport::Client' do + expect(controller.send(:client)).to be_instance_of(Gitlab::GithubImport::Client) + + get :status + end + end end describe "POST create" do diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index 909a4a797ae..10d6202cae0 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -19,7 +19,9 @@ import { } from '../mock_data'; import axios from '~/lib/utils/axios_utils'; import * as utils from '~/notes/stores/utils'; -import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql'; +import updateIssueConfidentialMutation from '~/sidebar/components/confidential/mutations/update_issue_confidential.mutation.graphql'; +import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql'; +import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql'; const TEST_ERROR_MESSAGE = 'Test error message'; jest.mock('~/flash'); @@ -1263,4 +1265,61 @@ describe('Actions Notes Store', () => { }); }); }); + + describe.each` + issuableType + ${'issue'} | ${'merge_request'} + `('updateLockedAttribute for issuableType=$issuableType', ({ issuableType }) => { + // Payload for mutation query + state = { noteableData: { discussion_locked: false } }; + const targetType = issuableType; + const getters = { getNoteableData: { iid: '1', targetType } }; + + // Target state after mutation + const locked = true; + const actionArgs = { fullPath: 'full/path', locked }; + const input = { iid: '1', projectPath: 'full/path', locked: true }; + + // Helper functions + const targetMutation = () => { + return targetType === 'issue' ? updateIssueLockMutation : updateMergeRequestLockMutation; + }; + + const mockResolvedValue = () => { + return targetType === 'issue' + ? { data: { issueSetLocked: { issue: { discussionLocked: locked } } } } + : { data: { mergeRequestSetLocked: { mergeRequest: { discussionLocked: locked } } } }; + }; + + beforeEach(() => { + jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(mockResolvedValue()); + }); + + it('calls gqClient mutation one time', () => { + actions.updateLockedAttribute({ commit: () => {}, state, getters }, actionArgs); + + expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1); + }); + + it('calls gqClient mutation with the correct values', () => { + actions.updateLockedAttribute({ commit: () => {}, state, getters }, actionArgs); + + expect(utils.gqClient.mutate).toHaveBeenCalledWith({ + mutation: targetMutation(), + variables: { input }, + }); + }); + + describe('on success of mutation', () => { + it('calls commit with the correct values', () => { + const commitSpy = jest.fn(); + + return actions + .updateLockedAttribute({ commit: commitSpy, state, getters }, actionArgs) + .then(() => { + expect(commitSpy).toHaveBeenCalledWith(mutationTypes.SET_ISSUABLE_LOCK, locked); + }); + }); + }); + }); }); diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js index 0ad18ba9b6a..344ebf9bbd9 100644 --- a/spec/frontend/notes/stores/mutation_spec.js +++ b/spec/frontend/notes/stores/mutation_spec.js @@ -833,13 +833,27 @@ describe('Notes Store mutations', () => { state = { noteableData: { confidential: false } }; }); - it('sets sort order', () => { + it('should set issuable as confidential', () => { mutations.SET_ISSUE_CONFIDENTIAL(state, true); expect(state.noteableData.confidential).toBe(true); }); }); + describe('SET_ISSUABLE_LOCK', () => { + let state; + + beforeEach(() => { + state = { noteableData: { discussion_locked: false } }; + }); + + it('should set issuable as locked', () => { + mutations.SET_ISSUABLE_LOCK(state, true); + + expect(state.noteableData.discussion_locked).toBe(true); + }); + }); + describe('UPDATE_ASSIGNEES', () => { it('should update assignees', () => { const state = { diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 1dca65dd862..cf2e6b00800 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -10,7 +10,7 @@ exports[`Repository last commit component renders commit widget 1`] = ` imgcssclasses="" imgsize="40" imgsrc="https://test.com" - linkhref="https://test.com/test" + linkhref="/test" tooltipplacement="top" tooltiptext="" username="" @@ -24,7 +24,7 @@ exports[`Repository last commit component renders commit widget 1`] = ` > <gl-link-stub class="commit-row-message item-title" - href="https://test.com/commit/123" + href="/commit/123" > Commit title </gl-link-stub> @@ -36,7 +36,7 @@ exports[`Repository last commit component renders commit widget 1`] = ` > <gl-link-stub class="commit-author-link js-user-link" - href="https://test.com/test" + href="/test" > Test @@ -110,7 +110,7 @@ exports[`Repository last commit component renders the signature HTML as returned imgcssclasses="" imgsize="40" imgsrc="https://test.com" - linkhref="https://test.com/test" + linkhref="/test" tooltipplacement="top" tooltiptext="" username="" @@ -124,7 +124,7 @@ exports[`Repository last commit component renders the signature HTML as returned > <gl-link-stub class="commit-row-message item-title" - href="https://test.com/commit/123" + href="/commit/123" > Commit title </gl-link-stub> @@ -136,7 +136,7 @@ exports[`Repository last commit component renders the signature HTML as returned > <gl-link-stub class="commit-author-link js-user-link" - href="https://test.com/test" + href="/test" > Test diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js index a5bfeb08fe4..c14a7f0e061 100644 --- a/spec/frontend/repository/components/last_commit_spec.js +++ b/spec/frontend/repository/components/last_commit_spec.js @@ -11,12 +11,12 @@ function createCommitData(data = {}) { title: 'Commit title', titleHtml: 'Commit title', message: 'Commit message', - webUrl: 'https://test.com/commit/123', + webPath: '/commit/123', authoredDate: '2019-01-01', author: { name: 'Test', avatarUrl: 'https://test.com', - webUrl: 'https://test.com/test', + webPath: '/test', }, pipeline: { detailedStatus: { @@ -108,7 +108,7 @@ describe('Repository last commit component', () => { }); it('does not render description expander when description is null', () => { - factory(createCommitData({ description: null })); + factory(createCommitData({ descriptionHtml: null })); return vm.vm.$nextTick(() => { expect(vm.find('.text-expander').exists()).toBe(false); @@ -117,7 +117,7 @@ describe('Repository last commit component', () => { }); it('expands commit description when clicking expander', () => { - factory(createCommitData({ description: 'Test description' })); + factory(createCommitData({ descriptionHtml: 'Test description' })); return vm.vm .$nextTick() diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js index ed50f292b8c..10669330b61 100644 --- a/spec/frontend/repository/components/table/index_spec.js +++ b/spec/frontend/repository/components/table/index_spec.js @@ -13,7 +13,7 @@ const MOCK_BLOBS = [ flatPath: 'blob', name: 'blob.md', type: 'blob', - webUrl: 'http://test.com', + webPath: '/blob', }, { id: '124abc', diff --git a/spec/frontend/sidebar/lock/constants.js b/spec/frontend/sidebar/lock/constants.js new file mode 100644 index 00000000000..b9f08e9286d --- /dev/null +++ b/spec/frontend/sidebar/lock/constants.js @@ -0,0 +1,2 @@ +export const ISSUABLE_TYPE_ISSUE = 'issue'; +export const ISSUABLE_TYPE_MR = 'merge request'; diff --git a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js index 66f9237ce97..df3cd16d3f7 100644 --- a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js +++ b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js @@ -1,31 +1,178 @@ import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; import EditFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue'; +import eventHub from '~/sidebar/event_hub'; +import flash from '~/flash'; +import createStore from '~/notes/stores'; +import { createStore as createMrStore } from '~/mr_notes/stores'; +import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants'; + +jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() })); +jest.mock('~/flash'); describe('EditFormButtons', () => { let wrapper; + let store; + let issuableType; + let issuableDisplayName; + + const setIssuableType = pageType => { + issuableType = pageType; + issuableDisplayName = issuableType.replace(/_/g, ' '); + }; + + const findLockToggle = () => wrapper.find('[data-testid="lock-toggle"]'); + const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon); - const mountComponent = propsData => shallowMount(EditFormButtons, { propsData }); + const createComponent = ({ props = {}, data = {}, resolved = true }) => { + store = issuableType === ISSUABLE_TYPE_ISSUE ? createStore() : createMrStore(); + + if (resolved) { + jest.spyOn(store, 'dispatch').mockResolvedValue(); + } else { + jest.spyOn(store, 'dispatch').mockRejectedValue(); + } + + wrapper = shallowMount(EditFormButtons, { + store, + provide: { + fullPath: '', + }, + propsData: { + isLocked: false, + issuableDisplayName, + ...props, + }, + data() { + return { + isLoading: false, + ...data, + }; + }, + }); + }; afterEach(() => { wrapper.destroy(); wrapper = null; }); - it('displays "Unlock" when locked', () => { - wrapper = mountComponent({ - isLocked: true, - updateLockedAttribute: () => {}, + describe.each` + pageType + ${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR} + `('In $pageType page', ({ pageType }) => { + beforeEach(() => { + setIssuableType(pageType); }); - expect(wrapper.text()).toContain('Unlock'); - }); + describe('when isLoading', () => { + beforeEach(() => { + createComponent({ data: { isLoading: true } }); + }); + + it('renders "Applying" in the toggle button', () => { + expect(findLockToggle().text()).toBe('Applying'); + }); + + it('disables the toggle button', () => { + expect(findLockToggle().attributes('disabled')).toBe('disabled'); + }); - it('displays "Lock" when unlocked', () => { - wrapper = mountComponent({ - isLocked: false, - updateLockedAttribute: () => {}, + it('displays the GlLoadingIcon', () => { + expect(findGlLoadingIcon().exists()).toBe(true); + }); }); - expect(wrapper.text()).toContain('Lock'); + describe.each` + isLocked | toggleText | statusText + ${false} | ${'Lock'} | ${'unlocked'} + ${true} | ${'Unlock'} | ${'locked'} + `('when $statusText', ({ isLocked, toggleText }) => { + beforeEach(() => { + createComponent({ + props: { + isLocked, + }, + }); + }); + + it(`toggle button displays "${toggleText}"`, () => { + expect(findLockToggle().text()).toContain(toggleText); + }); + + describe('when toggled', () => { + describe(`when resolved`, () => { + beforeEach(() => { + createComponent({ + props: { + isLocked, + }, + resolved: true, + }); + findLockToggle().trigger('click'); + }); + + it('dispatches the correct action', () => { + expect(store.dispatch).toHaveBeenCalledWith('updateLockedAttribute', { + locked: !isLocked, + fullPath: '', + }); + }); + + it('resets loading', async () => { + await wrapper.vm.$nextTick().then(() => { + expect(findGlLoadingIcon().exists()).toBe(false); + }); + }); + + it('emits close form', () => { + return wrapper.vm.$nextTick().then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm'); + }); + }); + + it('does not flash an error message', () => { + expect(flash).not.toHaveBeenCalled(); + }); + }); + + describe(`when not resolved`, () => { + beforeEach(() => { + createComponent({ + props: { + isLocked, + }, + resolved: false, + }); + findLockToggle().trigger('click'); + }); + + it('dispatches the correct action', () => { + expect(store.dispatch).toHaveBeenCalledWith('updateLockedAttribute', { + locked: !isLocked, + fullPath: '', + }); + }); + + it('resets loading', async () => { + await wrapper.vm.$nextTick().then(() => { + expect(findGlLoadingIcon().exists()).toBe(false); + }); + }); + + it('emits close form', () => { + return wrapper.vm.$nextTick().then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm'); + }); + }); + + it('calls flash with the correct message', () => { + expect(flash).toHaveBeenCalledWith( + `Something went wrong trying to change the locked state of this ${issuableDisplayName}`, + ); + }); + }); + }); + }); }); }); diff --git a/spec/frontend/sidebar/lock/edit_form_spec.js b/spec/frontend/sidebar/lock/edit_form_spec.js index ec10a999a40..6a86ab14d90 100644 --- a/spec/frontend/sidebar/lock/edit_form_spec.js +++ b/spec/frontend/sidebar/lock/edit_form_spec.js @@ -1,37 +1,56 @@ -import Vue from 'vue'; -import editForm from '~/sidebar/components/lock/edit_form.vue'; +import { shallowMount } from '@vue/test-utils'; +import EditForm from '~/sidebar/components/lock/edit_form.vue'; +import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants'; -describe('EditForm', () => { - let vm1; - let vm2; +describe('Edit Form Dropdown', () => { + let wrapper; + let issuableType; // Either ISSUABLE_TYPE_ISSUE or ISSUABLE_TYPE_MR + let issuableDisplayName; - beforeEach(() => { - const Component = Vue.extend(editForm); - const toggleForm = () => {}; - const updateLockedAttribute = () => {}; + const setIssuableType = pageType => { + issuableType = pageType; + issuableDisplayName = issuableType.replace(/_/g, ' '); + }; - vm1 = new Component({ - propsData: { - isLocked: true, - toggleForm, - updateLockedAttribute, - issuableType: 'issue', - }, - }).$mount(); + const findWarningText = () => wrapper.find('[data-testid="warning-text"]'); - vm2 = new Component({ + const createComponent = ({ props }) => { + wrapper = shallowMount(EditForm, { propsData: { isLocked: false, - toggleForm, - updateLockedAttribute, - issuableType: 'merge_request', + issuableDisplayName, + ...props, }, - }).$mount(); + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - it('renders on the appropriate warning text', () => { - expect(vm1.$el.innerHTML.includes('Unlock this issue?')).toBe(true); + describe.each` + pageType + ${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR} + `('In $pageType page', ({ pageType }) => { + beforeEach(() => { + setIssuableType(pageType); + }); + + describe.each` + isLocked | lockStatusText | lockAction | warningText + ${false} | ${'unlocked'} | ${'Lock'} | ${'Only project members will be able to comment.'} + ${true} | ${'locked'} | ${'Unlock'} | ${'Everyone will be able to comment.'} + `('when $lockStatusText', ({ isLocked, lockAction, warningText }) => { + beforeEach(() => { + createComponent({ props: { isLocked } }); + }); - expect(vm2.$el.innerHTML.includes('Lock this merge request?')).toBe(true); + it(`the appropriate warning text is rendered`, () => { + expect(findWarningText().text()).toContain( + `${lockAction} this ${issuableDisplayName}? ${warningText}`, + ); + }); + }); }); }); diff --git a/spec/frontend/sidebar/lock/lock_issue_sidebar_spec.js b/spec/frontend/sidebar/lock/lock_issue_sidebar_spec.js index 00997326d87..811e5207256 100644 --- a/spec/frontend/sidebar/lock/lock_issue_sidebar_spec.js +++ b/spec/frontend/sidebar/lock/lock_issue_sidebar_spec.js @@ -1,99 +1,145 @@ -import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; -import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue'; +import LockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue'; +import EditForm from '~/sidebar/components/lock/edit_form.vue'; +import createStore from '~/notes/stores'; +import { createStore as createMrStore } from '~/mr_notes/stores'; +import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants'; describe('LockIssueSidebar', () => { - let vm1; - let vm2; - - beforeEach(() => { - const Component = Vue.extend(lockIssueSidebar); - - const mediator = { + let wrapper; + let store; + let mediator; + let issuableType; // Either ISSUABLE_TYPE_ISSUE or ISSUABLE_TYPE_MR + + const setIssuableType = pageType => { + issuableType = pageType; + }; + + const findSidebarCollapseIcon = () => wrapper.find('[data-testid="sidebar-collapse-icon"]'); + const findLockStatus = () => wrapper.find('[data-testid="lock-status"]'); + const findEditLink = () => wrapper.find('[data-testid="edit-link"]'); + const findEditForm = () => wrapper.find(EditForm); + + const initMediator = () => { + mediator = { service: { update: Promise.resolve(true), }, - - store: { - isLockDialogOpen: false, - }, + store: {}, }; - - vm1 = new Component({ + }; + + const initStore = isLocked => { + if (issuableType === ISSUABLE_TYPE_ISSUE) { + store = createStore(); + store.getters.getNoteableData.targetType = 'issue'; + } else { + store = createMrStore(); + } + store.getters.getNoteableData.discussion_locked = isLocked; + }; + + const createComponent = ({ props = {} }) => { + wrapper = shallowMount(LockIssueSidebar, { + store, propsData: { - isLocked: true, isEditable: true, mediator, - issuableType: 'issue', + ...props, }, - }).$mount(); - - vm2 = new Component({ - propsData: { - isLocked: false, - isEditable: false, - mediator, - issuableType: 'merge_request', - }, - }).$mount(); - }); - - it('shows if locked and/or editable', () => { - expect(vm1.$el.innerHTML.includes('Edit')).toBe(true); - - expect(vm1.$el.innerHTML.includes('Locked')).toBe(true); - - expect(vm2.$el.innerHTML.includes('Unlocked')).toBe(true); - }); - - it('displays the edit form when editable', done => { - expect(vm1.isLockDialogOpen).toBe(false); - - vm1.$el.querySelector('.lock-edit').click(); - - expect(vm1.isLockDialogOpen).toBe(true); - - vm1.$nextTick(() => { - expect(vm1.$el.innerHTML.includes('Unlock this issue?')).toBe(true); - - done(); }); - }); - - it('tracks an event when "Edit" is clicked', () => { - const spy = mockTracking('_category_', vm1.$el, jest.spyOn); - triggerEvent('.lock-edit'); + }; - expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', { - label: 'right_sidebar', - property: 'lock_issue', - }); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - it('displays the edit form when opened from collapsed state', done => { - expect(vm1.isLockDialogOpen).toBe(false); - - vm1.$el.querySelector('.sidebar-collapsed-icon').click(); - - expect(vm1.isLockDialogOpen).toBe(true); - - setImmediate(() => { - expect(vm1.$el.innerHTML.includes('Unlock this issue?')).toBe(true); - - done(); + describe.each` + pageType + ${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR} + `('In $pageType page', ({ pageType }) => { + beforeEach(() => { + setIssuableType(pageType); + initMediator(); }); - }); - it('does not display the edit form when opened from collapsed state if not editable', done => { - expect(vm2.isLockDialogOpen).toBe(false); - - vm2.$el.querySelector('.sidebar-collapsed-icon').click(); - - Vue.nextTick() - .then(() => { - expect(vm2.isLockDialogOpen).toBe(false); - }) - .then(done) - .catch(done.fail); + describe.each` + isLocked + ${false} | ${true} + `(`renders for isLocked = $isLocked`, ({ isLocked }) => { + beforeEach(() => { + initStore(isLocked); + createComponent({}); + }); + + it('shows the lock status', () => { + expect(findLockStatus().text()).toBe(isLocked ? 'Locked' : 'Unlocked'); + }); + + describe('edit form', () => { + let isEditable; + beforeEach(() => { + isEditable = false; + createComponent({ props: { isEditable } }); + }); + + describe('when not editable', () => { + it('does not display the edit form when opened if not editable', () => { + expect(findEditForm().exists()).toBe(false); + findSidebarCollapseIcon().trigger('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditForm().exists()).toBe(false); + }); + }); + }); + + describe('when editable', () => { + beforeEach(() => { + isEditable = true; + createComponent({ props: { isEditable } }); + }); + + it('shows the editable status', () => { + expect(findEditLink().exists()).toBe(isEditable); + expect(findEditLink().text()).toBe('Edit'); + }); + + describe("when 'Edit' is clicked", () => { + it('displays the edit form when editable', () => { + expect(findEditForm().exists()).toBe(false); + findEditLink().trigger('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditForm().exists()).toBe(true); + }); + }); + + it('tracks the event ', () => { + const spy = mockTracking('_category_', wrapper.element, jest.spyOn); + triggerEvent(findEditLink().element); + + expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', { + label: 'right_sidebar', + property: 'lock_issue', + }); + }); + }); + + describe('When sidebar is collapsed', () => { + it('displays the edit form when opened', () => { + expect(findEditForm().exists()).toBe(false); + findSidebarCollapseIcon().trigger('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditForm().exists()).toBe(true); + }); + }); + }); + }); + }); + }); }); }); diff --git a/spec/lib/gitlab/usage_data/topology_spec.rb b/spec/lib/gitlab/usage_data/topology_spec.rb index 2a7adea261d..bf61ddd9d83 100644 --- a/spec/lib/gitlab/usage_data/topology_spec.rb +++ b/spec/lib/gitlab/usage_data/topology_spec.rb @@ -335,6 +335,40 @@ RSpec.describe Gitlab::UsageData::Topology do end end + context 'and unknown services are encountered' do + let(:unknown_service_process_count_response) do + [ + { + 'metric' => { 'instance' => 'instance2:9000', 'job' => 'unknown-service-A' }, + 'value' => [1000, '42'] + }, + { + 'metric' => { 'instance' => 'instance2:9001', 'job' => 'unknown-service-B' }, + 'value' => [1000, '42'] + } + ] + end + + it 'filters out unknown service data and reports the unknown services as a failure' do + expect_prometheus_api_to( + receive_app_request_volume_query(result: []), + receive_node_memory_query(result: []), + receive_node_cpu_count_query(result: []), + receive_node_uname_info_query(result: []), + receive_node_service_memory_rss_query(result: []), + receive_node_service_memory_uss_query(result: []), + receive_node_service_memory_pss_query(result: []), + receive_node_service_process_count_query(result: unknown_service_process_count_response), + receive_node_service_app_server_workers_query(result: []) + ) + + expect(subject.dig(:topology, :failures)).to include( + { 'service_unknown' => 'unknown-service-A' }, + { 'service_unknown' => 'unknown-service-B' } + ) + end + end + context 'and an error is raised when querying Prometheus' do it 'returns empty result with failures' do expect_prometheus_api_to receive(:query) @@ -534,11 +568,6 @@ RSpec.describe Gitlab::UsageData::Topology do { 'metric' => { 'instance' => 'instance2:8080', 'job' => 'registry' }, 'value' => [1000, '1'] - }, - # unknown service => should be stripped out - { - 'metric' => { 'instance' => 'instance2:9000', 'job' => 'not-a-gitlab-service' }, - 'value' => [1000, '42'] } ]) end diff --git a/spec/policies/personal_access_token_policy_spec.rb b/spec/policies/personal_access_token_policy_spec.rb new file mode 100644 index 00000000000..2236af81763 --- /dev/null +++ b/spec/policies/personal_access_token_policy_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe PersonalAccessTokenPolicy do + include AdminModeHelper + + using RSpec::Parameterized::TableSyntax + + where(:user_type, :owned_by_same_user, :expected_permitted?) do + :user | true | true + :user | false | false + :admin | false | true + end + + with_them do + context 'determine if a token is readable by a user' do + let(:user) { build_stubbed(user_type) } + let(:token_owner) { owned_by_same_user ? user : build(:user) } + let(:token) { build(:personal_access_token, user: token_owner) } + + subject { described_class.new(user, token) } + + before do + enable_admin_mode!(user) if user.admin? + end + + it { is_expected.to(expected_permitted? ? be_allowed(:read_token) : be_disallowed(:read_token)) } + end + end +end diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb index f026314f7a8..bbfb17fe753 100644 --- a/spec/requests/api/import_github_spec.rb +++ b/spec/requests/api/import_github_spec.rb @@ -22,7 +22,7 @@ RSpec.describe API::ImportGithub do before do Grape::Endpoint.before_each do |endpoint| - allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object) + allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repository: provider_repo).as_null_object) end end diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb index 266ff309662..713f8546d99 100644 --- a/spec/services/import/github_service_spec.rb +++ b/spec/services/import/github_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Import::GithubService do let_it_be(:user) { create(:user) } let_it_be(:token) { 'complex-token' } let_it_be(:access_params) { { github_access_token: 'github-complex-token' } } - let_it_be(:client) { Gitlab::LegacyGithubImport::Client.new(token) } + let_it_be(:client) { Gitlab::GithubImport::Client.new(token) } let_it_be(:params) { { repo_id: 123, new_name: 'new_repo', target_namespace: 'root' } } let(:subject) { described_class.new(client, user, params) } @@ -19,7 +19,7 @@ RSpec.describe Import::GithubService do let(:exception) { Octokit::ClientError.new(status: 404, body: 'Not Found') } before do - expect(client).to receive(:repo).and_raise(exception) + expect(client).to receive(:repository).and_raise(exception) end it 'logs the original error' do @@ -46,7 +46,7 @@ RSpec.describe Import::GithubService do it 'raises an exception for unknown error causes' do exception = StandardError.new('Not Implemented') - expect(client).to receive(:repo).and_raise(exception) + expect(client).to receive(:repository).and_raise(exception) expect(Gitlab::Import::Logger).not_to receive(:error) diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb index a01fa49d701..312dcc4b03c 100644 --- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb @@ -111,8 +111,11 @@ RSpec.shared_examples 'a GitHub-ish import controller: GET status' do end it "handles an invalid access token" do - allow_any_instance_of(Gitlab::LegacyGithubImport::Client) - .to receive(:repos).and_raise(Octokit::Unauthorized) + allow_any_instance_of(Gitlab::LegacyGithubImport::Client).to receive(:repos).and_raise(Octokit::Unauthorized) + + allow_next_instance_of(Octokit::Client) do |client| + allow(client).to receive(:repos).and_raise(Octokit::Unauthorized) + end get :status @@ -187,7 +190,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do end before do - stub_client(user: provider_user, repo: provider_repo) + stub_client(user: provider_user, repo: provider_repo, repository: provider_repo) assign_session_token(provider) end |