diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-15 21:12:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-15 21:12:21 +0300 |
commit | 4481a56a94c579f52e1cdef1cc1f4995f0ee1412 (patch) | |
tree | 9b8d431d53057e6351dd56718d212b2c89b19312 /spec | |
parent | e5c7d631a84940c66e46f1824ba7ce0c7f1d0ea4 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
16 files changed, 461 insertions, 57 deletions
diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index d88b816b186..c6d743ed38f 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -44,5 +44,6 @@ RSpec.describe 'Issues > User uses quick actions', :js do it_behaves_like 'move quick action' it_behaves_like 'zoom quick actions' it_behaves_like 'clone quick action' + it_behaves_like 'promote_to_incident quick action' end end diff --git a/spec/features/projects/cluster_agents_spec.rb b/spec/features/projects/cluster_agents_spec.rb index 98ee6ca161f..3ef710169f0 100644 --- a/spec/features/projects/cluster_agents_spec.rb +++ b/spec/features/projects/cluster_agents_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'ClusterAgents', :js do end it 'displays empty state', :aggregate_failures do - expect(page).to have_content('Connect with a GitLab Agent') + expect(page).to have_content('Install new Agent') expect(page).to have_selector('.empty-state') end end diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js index 3bd163c74eb..2dec7cdc973 100644 --- a/spec/frontend/clusters_list/components/agents_spec.js +++ b/spec/frontend/clusters_list/components/agents_spec.js @@ -14,7 +14,7 @@ localVue.use(VueApollo); describe('Agents', () => { let wrapper; - const propsData = { + const defaultProps = { defaultBranchName: 'default', }; const provideData = { @@ -22,12 +22,12 @@ describe('Agents', () => { kasAddress: 'kas.example.com', }; - const createWrapper = ({ agents = [], pageInfo = null, trees = [] }) => { + const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => { const provide = provideData; const apolloQueryResponse = { data: { project: { - clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] } }, + clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] }, count }, repository: { tree: { trees: { nodes: trees, pageInfo } } }, }, }, @@ -40,7 +40,10 @@ describe('Agents', () => { wrapper = shallowMount(Agents, { localVue, apolloProvider, - propsData, + propsData: { + ...defaultProps, + ...props, + }, provide: provideData, }); @@ -80,6 +83,8 @@ describe('Agents', () => { }, ]; + const count = 2; + const trees = [ { name: 'agent-2', @@ -120,7 +125,7 @@ describe('Agents', () => { ]; beforeEach(() => { - return createWrapper({ agents, trees }); + return createWrapper({ agents, count, trees }); }); it('should render agent table', () => { @@ -132,6 +137,10 @@ describe('Agents', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); }); + it('should emit agents count to the parent component', () => { + expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]); + }); + describe('when the agent has recently connected tokens', () => { it('should set agent status to active', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); @@ -179,6 +188,20 @@ describe('Agents', () => { it('should pass pageInfo to the pagination component', () => { expect(findPaginationButtons().props()).toMatchObject(pageInfo); }); + + describe('when limit is passed from the parent component', () => { + beforeEach(() => { + return createWrapper({ + props: { limit: 6 }, + agents, + pageInfo, + }); + }); + + it('should not render pagination buttons', () => { + expect(findPaginationButtons().exists()).toBe(false); + }); + }); }); }); @@ -235,7 +258,7 @@ describe('Agents', () => { beforeEach(() => { wrapper = shallowMount(Agents, { mocks, - propsData, + propsData: defaultProps, provide: provideData, }); diff --git a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js index bda0e6c110c..f7e1791d0f7 100644 --- a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js +++ b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js @@ -11,6 +11,10 @@ const canAddCluster = true; describe('ClustersEmptyStateComponent', () => { let wrapper; + const propsData = { + isChildComponent: false, + }; + const provideData = { clustersEmptyStateImage, emptyStateHelpText: null, @@ -27,6 +31,7 @@ describe('ClustersEmptyStateComponent', () => { beforeEach(() => { wrapper = shallowMountExtended(ClustersEmptyState, { store: ClusterStore(entryData), + propsData, provide: provideData, stubs: { GlEmptyState }, }); @@ -36,8 +41,10 @@ describe('ClustersEmptyStateComponent', () => { wrapper.destroy(); }); - it('should render the action button', () => { - expect(findButton().exists()).toBe(true); + describe('when the component is loaded independently', () => { + it('should render the action button', () => { + expect(findButton().exists()).toBe(true); + }); }); describe('when the help text is not provided', () => { @@ -46,11 +53,31 @@ describe('ClustersEmptyStateComponent', () => { }); }); + describe('when the component is loaded as a child component', () => { + beforeEach(() => { + propsData.isChildComponent = true; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + }); + }); + + afterEach(() => { + propsData.isChildComponent = false; + }); + + it('should not render the action button', () => { + expect(findButton().exists()).toBe(false); + }); + }); + describe('when the help text is provided', () => { beforeEach(() => { provideData.emptyStateHelpText = emptyStateHelpText; wrapper = shallowMountExtended(ClustersEmptyState, { store: ClusterStore(entryData), + propsData, provide: provideData, }); }); @@ -61,8 +88,14 @@ describe('ClustersEmptyStateComponent', () => { }); describe('when the user cannot add clusters', () => { + entryData.canAddCluster = false; beforeEach(() => { - wrapper.vm.$store.state.canAddCluster = false; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + stubs: { GlEmptyState }, + }); }); it('should disable the button', () => { expect(findButton().props('disabled')).toBe(true); diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js index 3869a2a8736..c2233e5d39c 100644 --- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js +++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js @@ -1,7 +1,14 @@ import { GlTabs, GlTab } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue'; -import { CLUSTERS_TABS } from '~/clusters_list/constants'; +import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; +import { + AGENT, + CERTIFICATE_BASED, + CLUSTERS_TABS, + MAX_CLUSTERS_LIST, + MAX_LIST_COUNT, +} from '~/clusters_list/constants'; const defaultBranchName = 'default-branch'; @@ -26,6 +33,7 @@ describe('ClustersMainViewComponent', () => { const findAllTabs = () => wrapper.findAllComponents(GlTab); const findGlTabAtIndex = (index) => findAllTabs().at(index); const findComponent = () => wrapper.findByTestId('clusters-tab-component'); + const findModal = () => wrapper.findComponent(InstallAgentModal); it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => { expect(findTabs().exists()).toBe(true); @@ -40,11 +48,16 @@ describe('ClustersMainViewComponent', () => { expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName); }); + it('passes correct max-agents param to the modal', () => { + expect(findModal().props('maxAgents')).toBe(MAX_CLUSTERS_LIST); + }); + describe('tabs', () => { it.each` - tabTitle | queryParamValue | lineNumber - ${'Agent'} | ${'agent'} | ${0} - ${'Certificate based'} | ${'certificate_based'} | ${1} + tabTitle | queryParamValue | lineNumber + ${'All'} | ${'all'} | ${0} + ${'Agent'} | ${AGENT} | ${1} + ${'Certificate based'} | ${CERTIFICATE_BASED} | ${2} `( 'renders correct tab title and query param value', ({ tabTitle, queryParamValue, lineNumber }) => { @@ -53,4 +66,17 @@ describe('ClustersMainViewComponent', () => { }, ); }); + + describe('when the child component emits the tab change event', () => { + beforeEach(() => { + findComponent().vm.$emit('changeTab', AGENT); + }); + it('changes the tab', () => { + expect(findTabs().attributes('value')).toBe('1'); + }); + + it('passes correct max-agents param to the modal', () => { + expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT); + }); + }); }); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index de511788b08..a34202c789d 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -48,9 +48,9 @@ describe('Clusters', () => { mock.onGet(`${endpoint}?page=${header['x-page']}`).reply(response, body, header); }; - const mountWrapper = () => { + const createWrapper = ({ propsData = {} }) => { store = ClusterStore(entryData); - wrapper = mount(Clusters, { provide: provideData, store, stubs: { GlTable } }); + wrapper = mount(Clusters, { propsData, provide: provideData, store, stubs: { GlTable } }); return axios.waitForAll(); }; @@ -70,7 +70,7 @@ describe('Clusters', () => { mock = new MockAdapter(axios); mockPollingApi(200, apiData, paginationHeader()); - return mountWrapper(); + return createWrapper({}); }); afterEach(() => { @@ -105,6 +105,16 @@ describe('Clusters', () => { expect(findEmptyState().exists()).toBe(true); }); }); + + describe('when is loaded as a child component', () => { + beforeEach(() => { + createWrapper({ limit: 6 }); + }); + + it("shouldn't render pagination buttons", () => { + expect(findPaginatedButtons().exists()).toBe(false); + }); + }); }); describe('cluster icon', () => { @@ -248,7 +258,7 @@ describe('Clusters', () => { beforeEach(() => { mockPollingApi(200, apiData, paginationHeader(totalFirstPage, perPage, 1)); - return mountWrapper(); + return createWrapper({}); }); it('should load to page 1 with header values', () => { diff --git a/spec/frontend/clusters_list/components/clusters_view_all_spec.js b/spec/frontend/clusters_list/components/clusters_view_all_spec.js new file mode 100644 index 00000000000..6ef56beddee --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_view_all_spec.js @@ -0,0 +1,243 @@ +import { GlCard, GlLoadingIcon, GlButton, GlSprintf, GlBadge } from '@gitlab/ui'; +import { createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersViewAll from '~/clusters_list/components/clusters_view_all.vue'; +import Agents from '~/clusters_list/components/agents.vue'; +import Clusters from '~/clusters_list/components/clusters.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { + AGENT, + CERTIFICATE_BASED, + AGENT_CARD_INFO, + CERTIFICATE_BASED_CARD_INFO, + MAX_CLUSTERS_LIST, + INSTALL_AGENT_MODAL_ID, +} from '~/clusters_list/constants'; +import { sprintf } from '~/locale'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +const addClusterPath = '/path/to/add/cluster'; +const defaultBranchName = 'default-branch'; + +describe('ClustersViewAllComponent', () => { + let wrapper; + + const event = { + preventDefault: jest.fn(), + }; + + const propsData = { + defaultBranchName, + }; + + const provideData = { + addClusterPath, + }; + + const entryData = { + loadingClusters: false, + totalClusters: 0, + }; + + const findCards = () => wrapper.findAllComponents(GlCard); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAgentsComponent = () => wrapper.findComponent(Agents); + const findClustersComponent = () => wrapper.findComponent(Clusters); + const findCardsContainer = () => wrapper.findByTestId('clusters-cards-container'); + const findAgentCardTitle = () => wrapper.findByTestId('agent-card-title'); + const findRecommendedBadge = () => wrapper.findComponent(GlBadge); + const findClustersCardTitle = () => wrapper.findByTestId('clusters-card-title'); + const findFooterButton = (line) => findCards().at(line).findComponent(GlButton); + + const createStore = (initialState) => + new Vuex.Store({ + state: initialState, + }); + + const createWrapper = ({ initialState }) => { + wrapper = shallowMountExtended(ClustersViewAll, { + localVue, + store: createStore(initialState), + propsData, + provide: provideData, + directives: { + GlModalDirective: createMockDirective(), + }, + stubs: { GlCard, GlSprintf }, + }); + }; + + beforeEach(() => { + createWrapper({ initialState: entryData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when agents and clusters are not loaded', () => { + const initialState = { + loadingClusters: true, + totalClusters: 0, + }; + beforeEach(() => { + createWrapper({ initialState }); + }); + + it('should show the loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + }); + + describe('when both agents and clusters are loaded', () => { + beforeEach(() => { + findAgentsComponent().vm.$emit('onAgentsLoad', 6); + }); + + it("shouldn't show the loading icon", () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + + it('should make content visible', () => { + expect(findCardsContainer().isVisible()).toBe(true); + }); + + it('should render 2 cards', () => { + expect(findCards().length).toBe(2); + }); + }); + + describe('agents card', () => { + it('should show recommended badge', () => { + expect(findRecommendedBadge().exists()).toBe(true); + }); + + it('should render Agents component', () => { + expect(findAgentsComponent().exists()).toBe(true); + }); + + it('should pass the limit prop', () => { + expect(findAgentsComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the default-branch-name prop', () => { + expect(findAgentsComponent().props('defaultBranchName')).toBe(defaultBranchName); + }); + + describe('when there are no agents', () => { + it('should show the empty title', () => { + expect(findAgentCardTitle().text()).toBe(AGENT_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(0).exists()).toBe(true); + }); + + it('should render correct modal id for the agent link', () => { + const binding = getBinding(findFooterButton(0).element, 'gl-modal-directive'); + + expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID); + }); + }); + + describe('when the agents are present', () => { + const findFooterLink = () => wrapper.findByTestId('agents-tab-footer-link'); + const agentsNumber = 7; + + beforeEach(() => { + findAgentsComponent().vm.$emit('onAgentsLoad', agentsNumber); + }); + + it('should show the correct title', () => { + expect(findAgentCardTitle().text()).toBe( + sprintf(AGENT_CARD_INFO.title, { number: MAX_CLUSTERS_LIST, total: agentsNumber }), + ); + }); + + it('should show the link to the Agents tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(AGENT_CARD_INFO.footerText, { number: agentsNumber }), + ); + expect(findFooterLink().attributes('href')).toBe(`?tab=${AGENT}`); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(wrapper.emitted('changeTab')).toEqual([[AGENT]]); + }); + }); + }); + }); + + describe('clusters tab', () => { + it('should pass the limit prop', () => { + expect(findClustersComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the is-child-component prop', () => { + expect(findClustersComponent().props('isChildComponent')).toBe(true); + }); + + describe('when there are no clusters', () => { + it('should show the empty title', () => { + expect(findClustersCardTitle().text()).toBe(CERTIFICATE_BASED_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(1).exists()).toBe(true); + }); + + it('should render correct href for the button in the footer', () => { + expect(findFooterButton(1).attributes('href')).toBe(addClusterPath); + }); + }); + + describe('when the clusters are present', () => { + const findFooterLink = () => wrapper.findByTestId('clusters-tab-footer-link'); + + const clustersNumber = 7; + const initialState = { + loadingClusters: false, + totalClusters: clustersNumber, + }; + + beforeEach(() => { + createWrapper({ initialState }); + }); + + it('should show the correct title', () => { + expect(findClustersCardTitle().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.title, { + number: MAX_CLUSTERS_LIST, + total: clustersNumber, + }), + ); + }); + + it('should show the link to the Clusters tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.footerText, { number: clustersNumber }), + ); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(wrapper.emitted('changeTab')).toEqual([[CERTIFICATE_BASED]]); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js index 89c99bb260e..6c2ea45b99b 100644 --- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js +++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js @@ -24,6 +24,7 @@ localVue.use(VueApollo); const projectPath = 'path/to/project'; const defaultBranchName = 'default'; +const maxAgents = MAX_LIST_COUNT; describe('InstallAgentModal', () => { let wrapper; @@ -56,6 +57,7 @@ describe('InstallAgentModal', () => { const propsData = { defaultBranchName, + maxAgents, }; wrapper = shallowMount(InstallAgentModal, { diff --git a/spec/frontend/clusters_list/mocks/apollo.js b/spec/frontend/clusters_list/mocks/apollo.js index eeed494883f..1a7ef84a6d9 100644 --- a/spec/frontend/clusters_list/mocks/apollo.js +++ b/spec/frontend/clusters_list/mocks/apollo.js @@ -16,6 +16,7 @@ const pageInfo = { hasPreviousPage: false, startCursor: '', }; +const count = 1; export const createAgentResponse = { data: { @@ -64,7 +65,7 @@ export const createAgentTokenErrorResponse = { export const getAgentResponse = { data: { project: { - clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo }, + clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo, count }, repository: { tree: { trees: { nodes: [{ ...agent, path: null }], pageInfo }, diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js index f8723bfcdfc..ae264eee449 100644 --- a/spec/frontend/clusters_list/store/mutations_spec.js +++ b/spec/frontend/clusters_list/store/mutations_spec.js @@ -57,4 +57,12 @@ describe('Admin statistics panel mutations', () => { expect(state.page).toBe(123); }); }); + + describe(`${types.SET_CLUSTERS_PER_PAGE}`, () => { + it('changes clustersPerPage value', () => { + mutations[types.SET_CLUSTERS_PER_PAGE](state, 123); + + expect(state.clustersPerPage).toBe(123); + }); + }); }); diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb index 865e892b12d..3fcfa967452 100644 --- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -20,23 +20,37 @@ RSpec.describe ResolvesPipelines do let_it_be(:project) { create(:project, :private) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) } let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } let_it_be(:sha_pipeline) { create(:ci_pipeline, project: project, sha: 'deadbeef') } + let_it_be(:all_pipelines) do + [ + pipeline, + failed_pipeline, + success_pipeline, + ref_pipeline, + sha_pipeline + ] + end before do project.add_developer(current_user) end - it { is_expected.to have_graphql_arguments(:status, :ref, :sha, :source) } + it { is_expected.to have_graphql_arguments(:status, :scope, :ref, :sha, :source) } it 'finds all pipelines' do - expect(resolve_pipelines).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline) + expect(resolve_pipelines).to contain_exactly(*all_pipelines) end it 'allows filtering by status' do expect(resolve_pipelines(status: 'failed')).to contain_exactly(failed_pipeline) end + it 'allows filtering by scope' do + expect(resolve_pipelines(scope: 'finished')).to contain_exactly(failed_pipeline, success_pipeline) + end + it 'allows filtering by ref' do expect(resolve_pipelines(ref: 'awesome-feature')).to contain_exactly(ref_pipeline) end @@ -54,7 +68,7 @@ RSpec.describe ResolvesPipelines do end it 'does not filter by source' do - expect(resolve_pipelines(source: 'web')).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline, source_pipeline) + expect(resolve_pipelines(source: 'web')).to contain_exactly(*all_pipelines, source_pipeline) end end @@ -64,7 +78,7 @@ RSpec.describe ResolvesPipelines do end it 'returns all the pipelines' do - expect(resolve_pipelines).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline, source_pipeline) + expect(resolve_pipelines).to contain_exactly(*all_pipelines, source_pipeline) end end end diff --git a/spec/graphql/types/ci/pipeline_scope_enum_spec.rb b/spec/graphql/types/ci/pipeline_scope_enum_spec.rb new file mode 100644 index 00000000000..9dc6e5c6fae --- /dev/null +++ b/spec/graphql/types/ci/pipeline_scope_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::PipelineScopeEnum do + it 'exposes all pipeline scopes' do + expect(described_class.values.keys).to contain_exactly( + *::Ci::PipelinesFinder::ALLOWED_SCOPES.keys.map(&:to_s) + ) + end +end diff --git a/spec/graphql/types/ci/pipeline_status_enum_spec.rb b/spec/graphql/types/ci/pipeline_status_enum_spec.rb new file mode 100644 index 00000000000..2d6683c6384 --- /dev/null +++ b/spec/graphql/types/ci/pipeline_status_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::PipelineStatusEnum do + it 'exposes all pipeline states' do + expect(described_class.values.keys).to contain_exactly( + *::Ci::Pipeline.all_state_names.map(&:to_s).map(&:upcase) + ) + end +end diff --git a/spec/initializers/100_patch_omniauth_oauth2_spec.rb b/spec/initializers/100_patch_omniauth_oauth2_spec.rb index 0c436e4ef45..c30a1cdeafa 100644 --- a/spec/initializers/100_patch_omniauth_oauth2_spec.rb +++ b/spec/initializers/100_patch_omniauth_oauth2_spec.rb @@ -2,12 +2,10 @@ require 'spec_helper' -RSpec.describe 'OmniAuth::Strategies::OAuth2', type: :strategy do - let(:strategy) { [OmniAuth::Strategies::OAuth2] } - +RSpec.describe 'OmniAuth::Strategies::OAuth2' do it 'verifies the gem version' do current_version = OmniAuth::OAuth2::VERSION - expected_version = '1.7.1' + expected_version = '1.7.2' expect(current_version).to eq(expected_version), <<~EOF New version #{current_version} of the `omniauth-oauth2` gem detected! @@ -18,39 +16,18 @@ RSpec.describe 'OmniAuth::Strategies::OAuth2', type: :strategy do EOF end - context 'when a custom error message is passed from an OAuth2 provider' do - let(:message) { 'Please go to https://evil.com' } - let(:state) { 'secret' } - let(:callback_path) { '/users/auth/oauth2/callback' } - let(:params) { { state: state, error: 'evil_key', error_description: message } } - let(:error) { last_request.env['omniauth.error'] } - - before do - env('rack.session', { 'omniauth.state' => state }) - end - - it 'returns the custom error message if the state is valid' do - get callback_path, **params - - expect(error.message).to eq("evil_key | #{message}") - end + context 'when a Faraday exception is raised' do + where(exception: [Faraday::TimeoutError, Faraday::ConnectionFailed]) - it 'returns the custom `error_reason` message if the `error_description` is blank' do - get callback_path, **params.merge(error_description: ' ', error_reason: 'custom reason') - - expect(error.message).to eq('evil_key | custom reason') - end - - it 'returns a CSRF error if the state is invalid' do - get callback_path, **params.merge(state: 'invalid') - - expect(error.message).to eq('csrf_detected | CSRF detected') - end + with_them do + it 'passes the exception to OmniAuth' do + instance = OmniAuth::Strategies::OAuth2.new(double) - it 'returns a CSRF error if the state is missing' do - get callback_path, **params.without(:state) + expect(instance).to receive(:original_callback_phase) { raise exception, 'message' } + expect(instance).to receive(:fail!).with(:timeout, kind_of(exception)) - expect(error.message).to eq('csrf_detected | CSRF detected') + instance.callback_phase + end end end end diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 4ba6c6e50f7..c254279a32f 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -57,4 +57,8 @@ RSpec.describe Ci::Trigger do it { is_expected.to eq(false) } end end + + it_behaves_like 'includes Limitable concern' do + subject { build(:ci_trigger, owner: project.owner, project: project) } + end end diff --git a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb new file mode 100644 index 00000000000..5167d27f8b9 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'promote_to_incident quick action' do + describe '/promote_to_incident' do + context 'when issue can be promoted' do + it 'promotes issue to incident' do + add_note('/promote_to_incident') + + expect(issue.reload.issue_type).to eq('incident') + expect(page).to have_content('Issue has been promoted to incident') + end + end + + context 'when issue is already an incident' do + let(:issue) { create(:incident, project: project) } + + it 'does not promote the issue' do + add_note('/promote_to_incident') + + expect(page).to have_content('Could not apply promote_to_incident command') + end + end + + context 'when user does not have permissions' do + let(:guest) { create(:user) } + + before do + sign_in(guest) + visit project_issue_path(project, issue) + wait_for_all_requests + end + + it 'does not promote the issue' do + add_note('/promote_to_incident') + + expect(page).to have_content('Could not apply promote_to_incident command') + end + end + end +end |