diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-15 15:09:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-15 15:09:29 +0300 |
commit | 6986c1adc235859111e45593bb0bd61e70892d3c (patch) | |
tree | 99912b55cd4c39c7ce24120269dfc870ff8f3705 /spec | |
parent | c5b1e86b43f136d8a43cab867ddc49a02751c45a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/features/projects/terraform_spec.rb | 2 | ||||
-rw-r--r-- | spec/frontend/issuable_list/components/issuable_item_spec.js | 46 | ||||
-rw-r--r-- | spec/frontend/terraform/components/states_table_actions_spec.js | 75 | ||||
-rw-r--r-- | spec/frontend/terraform/components/states_table_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/vue_shared/components/toggle_button_spec.js | 96 | ||||
-rw-r--r-- | spec/graphql/resolvers/group_labels_resolver_spec.rb | 96 | ||||
-rw-r--r-- | spec/graphql/resolvers/labels_resolver_spec.rb | 96 | ||||
-rw-r--r-- | spec/graphql/types/group_type_spec.rb | 4 | ||||
-rw-r--r-- | spec/graphql/types/project_type_spec.rb | 4 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/url_blocker_spec.rb | 41 | ||||
-rw-r--r-- | spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb | 86 | ||||
-rw-r--r-- | spec/support/helpers/dns_helpers.rb | 35 | ||||
-rw-r--r-- | spec/support/helpers/stub_requests.rb | 19 | ||||
-rw-r--r-- | spec/support/shared_examples/graphql/label_fields.rb | 2 |
15 files changed, 464 insertions, 167 deletions
diff --git a/spec/features/projects/terraform_spec.rb b/spec/features/projects/terraform_spec.rb index dfa4dad8490..55b906c2bc5 100644 --- a/spec/features/projects/terraform_spec.rb +++ b/spec/features/projects/terraform_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Terraform', :js do fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name click_button 'Remove' - expect(page).not_to have_content(additional_state.name) + expect(page).to have_content("#{additional_state.name} successfully removed") expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/frontend/issuable_list/components/issuable_item_spec.js b/spec/frontend/issuable_list/components/issuable_item_spec.js index d6e6816086e..987acf559e3 100644 --- a/spec/frontend/issuable_list/components/issuable_item_spec.js +++ b/spec/frontend/issuable_list/components/issuable_item_spec.js @@ -18,6 +18,8 @@ const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots slots, }); +const MOCK_GITLAB_URL = 'http://0.0.0.0:3000'; + describe('IssuableItem', () => { // The mock data is dependent that this is after our default date useFakeDate(2020, 11, 11); @@ -28,7 +30,7 @@ describe('IssuableItem', () => { let wrapper; beforeEach(() => { - gon.gitlab_url = 'http://0.0.0.0:3000'; + gon.gitlab_url = MOCK_GITLAB_URL; wrapper = createComponent(); }); @@ -73,11 +75,11 @@ describe('IssuableItem', () => { describe('isIssuableUrlExternal', () => { it.each` - issuableWebUrl | urlType | returnValue - ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false} - ${'http://0.0.0.0:3000/gitlab-org/gitlab-test/-/issues/1'} | ${'absolute and internal'} | ${false} - ${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true} - ${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true} + issuableWebUrl | urlType | returnValue + ${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false} + ${`${MOCK_GITLAB_URL}/gitlab-org/gitlab-test/-/issues/1`} | ${'absolute and internal'} | ${false} + ${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true} + ${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true} `( 'returns $returnValue when `issuable.webUrl` is $urlType', async ({ issuableWebUrl, returnValue }) => { @@ -217,14 +219,32 @@ describe('IssuableItem', () => { }); describe('template', () => { - it('renders issuable title', () => { - const titleEl = wrapper.find('[data-testid="issuable-title"]'); + it.each` + gitlabWebUrl | webUrl | expectedHref | expectedTarget + ${undefined} | ${`${MOCK_GITLAB_URL}/issue`} | ${`${MOCK_GITLAB_URL}/issue`} | ${undefined} + ${undefined} | ${'https://jira.com/issue'} | ${'https://jira.com/issue'} | ${'_blank'} + ${'/gitlab-org/issue'} | ${'https://jira.com/issue'} | ${'/gitlab-org/issue'} | ${undefined} + `( + 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`', + async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => { + wrapper.setProps({ + issuable: { + ...mockIssuable, + webUrl, + gitlabWebUrl, + }, + }); - expect(titleEl.exists()).toBe(true); - expect(titleEl.find(GlLink).attributes('href')).toBe(mockIssuable.webUrl); - expect(titleEl.find(GlLink).attributes('target')).not.toBeDefined(); - expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title); - }); + await wrapper.vm.$nextTick(); + + const titleEl = wrapper.find('[data-testid="issuable-title"]'); + + expect(titleEl.exists()).toBe(true); + expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref); + expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget); + expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title); + }, + ); it('renders checkbox when `showCheckbox` prop is true', async () => { wrapper.setProps({ diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js index 7449ddd3d2a..61f6e9f0f7b 100644 --- a/spec/frontend/terraform/components/states_table_actions_spec.js +++ b/spec/frontend/terraform/components/states_table_actions_spec.js @@ -14,6 +14,7 @@ localVue.use(VueApollo); describe('StatesTableActions', () => { let lockResponse; let removeResponse; + let toast; let unlockResponse; let updateStateResponse; let wrapper; @@ -59,10 +60,13 @@ describe('StatesTableActions', () => { const createComponent = (propsData = defaultProps) => { const apolloProvider = createMockApolloProvider(); + toast = jest.fn(); + wrapper = shallowMount(StateActions, { apolloProvider, localVue, propsData, + mocks: { $toast: { show: toast } }, stubs: { GlDropdown, GlModal, GlSprintf }, }); @@ -83,6 +87,7 @@ describe('StatesTableActions', () => { afterEach(() => { lockResponse = null; removeResponse = null; + toast = null; unlockResponse = null; updateStateResponse = null; wrapper.destroy(); @@ -243,7 +248,6 @@ describe('StatesTableActions', () => { describe('when clicking the remove button', () => { beforeEach(() => { findRemoveBtn().vm.$emit('click'); - return waitForPromises(); }); @@ -254,21 +258,70 @@ describe('StatesTableActions', () => { }); describe('when submitting the remove modal', () => { - it('does not call the remove mutation when state name is missing', async () => { - findRemoveModal().vm.$emit('ok'); - await wrapper.vm.$nextTick(); + describe('when state name is missing', () => { + beforeEach(() => { + findRemoveModal().vm.$emit('ok'); + return waitForPromises(); + }); - expect(removeResponse).not.toHaveBeenCalledWith(); + it('does not call the remove mutation', () => { + expect(removeResponse).not.toHaveBeenCalledWith(); + }); }); - it('calls the remove mutation when state name is present', async () => { - await wrapper.setData({ removeConfirmText: defaultProps.state.name }); + describe('when state name is present', () => { + beforeEach(async () => { + await wrapper.setData({ removeConfirmText: defaultProps.state.name }); + + findRemoveModal().vm.$emit('ok'); - findRemoveModal().vm.$emit('ok'); - await wrapper.vm.$nextTick(); + await waitForPromises(); + }); + + it('calls the remove mutation', () => { + expect(removeResponse).toHaveBeenCalledWith({ stateID: defaultProps.state.id }); + }); + + it('calls the toast action', () => { + expect(toast).toHaveBeenCalledWith(`${defaultProps.state.name} successfully removed`); + }); - expect(removeResponse).toHaveBeenCalledWith({ - stateID: defaultProps.state.id, + it('calls mutations to set loading and errors', () => { + // loading update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 1, + {}, + { + terraformState: { + ...defaultProps.state, + _showDetails: false, + errorMessages: [], + loadingLock: false, + loadingRemove: true, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); + + // final update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 2, + {}, + { + terraformState: { + ...defaultProps.state, + _showDetails: false, + errorMessages: [], + loadingLock: false, + loadingRemove: false, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); }); }); }); diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js index a9aeba202e0..100e577f514 100644 --- a/spec/frontend/terraform/components/states_table_spec.js +++ b/spec/frontend/terraform/components/states_table_spec.js @@ -1,4 +1,4 @@ -import { GlIcon, GlTooltip } from '@gitlab/ui'; +import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; import StatesTable from '~/terraform/components/states_table.vue'; @@ -92,6 +92,17 @@ describe('StatesTable', () => { }, }, }, + { + _showDetails: false, + errorMessages: [], + name: 'state-5', + loadingLock: false, + loadingRemove: true, + lockedAt: null, + lockedByUser: null, + updatedAt: '2020-10-10T00:00:00Z', + latestVersion: null, + }, ], }; @@ -112,14 +123,15 @@ describe('StatesTable', () => { }); it.each` - name | toolTipText | locked | lineNumber - ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0} - ${'state-2'} | ${'Locking state'} | ${false} | ${1} - ${'state-3'} | ${'Unlocking state'} | ${false} | ${2} - ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3} + name | toolTipText | locked | loading | lineNumber + ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0} + ${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1} + ${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2} + ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3} + ${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4} `( 'displays the name and locked information "$name" for line "$lineNumber"', - ({ name, toolTipText, locked, lineNumber }) => { + ({ name, toolTipText, locked, loading, lineNumber }) => { const states = wrapper.findAll('[data-testid="terraform-states-table-name"]'); const state = states.at(lineNumber); @@ -127,6 +139,7 @@ describe('StatesTable', () => { expect(state.text()).toContain(name); expect(state.find(GlIcon).exists()).toBe(locked); + expect(state.find(GlLoadingIcon).exists()).toBe(loading); expect(toolTip.exists()).toBe(locked); if (locked) { diff --git a/spec/frontend/vue_shared/components/toggle_button_spec.js b/spec/frontend/vue_shared/components/toggle_button_spec.js deleted file mode 100644 index 632e648aadc..00000000000 --- a/spec/frontend/vue_shared/components/toggle_button_spec.js +++ /dev/null @@ -1,96 +0,0 @@ -import { GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import ToggleButton from '~/vue_shared/components/toggle_button.vue'; - -describe('Toggle Button component', () => { - let wrapper; - - function createComponent(propsData = {}) { - wrapper = shallowMount(ToggleButton, { - propsData, - }); - } - - const findInput = () => wrapper.find('input'); - const findButton = () => wrapper.find('button'); - const findToggleIcon = () => wrapper.find(GlIcon); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('renders input with provided name', () => { - createComponent({ - name: 'foo', - }); - - expect(findInput().attributes('name')).toBe('foo'); - }); - - describe.each` - value | iconName - ${true} | ${'status_success_borderless'} - ${false} | ${'status_failed_borderless'} - `('when `value` prop is `$value`', ({ value, iconName }) => { - beforeEach(() => { - createComponent({ - value, - name: 'foo', - }); - }); - - it('renders input with correct value attribute', () => { - expect(findInput().attributes('value')).toBe(`${value}`); - }); - - it('renders correct icon', () => { - const icon = findToggleIcon(); - expect(icon.isVisible()).toBe(true); - expect(icon.props('name')).toBe(iconName); - expect(findButton().classes('is-checked')).toBe(value); - }); - - describe('when clicked', () => { - it('emits `change` event with correct event', async () => { - findButton().trigger('click'); - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted('change')).toStrictEqual([[!value]]); - }); - }); - }); - - describe('when `disabledInput` prop is `true`', () => { - beforeEach(() => { - createComponent({ - value: true, - disabledInput: true, - }); - }); - - it('renders disabled button', () => { - expect(findButton().classes()).toContain('is-disabled'); - }); - - it('does not emit change event when clicked', async () => { - findButton().trigger('click'); - await wrapper.vm.$nextTick(); - - expect(wrapper.emitted('change')).toBeFalsy(); - }); - }); - - describe('when `isLoading` prop is `true`', () => { - beforeEach(() => { - createComponent({ - value: true, - isLoading: true, - }); - }); - - it('renders loading class', () => { - expect(findButton().classes()).toContain('is-loading'); - }); - }); -}); diff --git a/spec/graphql/resolvers/group_labels_resolver_spec.rb b/spec/graphql/resolvers/group_labels_resolver_spec.rb new file mode 100644 index 00000000000..ed94f12502a --- /dev/null +++ b/spec/graphql/resolvers/group_labels_resolver_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::GroupLabelsResolver do + include GraphqlHelpers + + using RSpec::Parameterized::TableSyntax + + let_it_be(:current_user) { create(:user) } + let_it_be(:group, reload: true) { create(:group, :private) } + let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) } + let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) } + let_it_be(:project, reload: true) { create(:project, :private, group: sub_subgroup) } + let_it_be(:label1) { create(:label, project: project, name: 'project feature') } + let_it_be(:label2) { create(:label, project: project, name: 'new project feature') } + let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') } + let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') } + let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') } + let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') } + let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') } + let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'raises error' do + expect { resolve_labels(subgroup) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with authorized user' do + it 'does not raise error' do + group.add_guest(current_user) + + expect { resolve_labels(subgroup) }.not_to raise_error + end + end + + context 'without parent' do + it 'returns no labels' do + expect(resolve_labels(nil)).to eq(Label.none) + end + end + + context 'at group level' do + before_all do + group.add_developer(current_user) + end + + # because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false + # the `nil` value would be equivalent to passing in `false` so just check for `nil` option + where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do + nil | nil | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) } + nil | nil | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) } + nil | true | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) } + nil | true | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | nil | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | nil | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | true | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) } + true | true | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + + nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) } + nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) } + nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2, label2) } + nil | true | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2) } + true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) } + true | nil | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) } + true | true | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2, label2) } + true | true | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2) } + end + + with_them do + let(:params) do + { + include_ancestor_groups: include_ancestor_groups, + include_descendant_groups: include_descendant_groups, + only_group_labels: only_group_labels, + search_term: search_term + } + end + + subject { resolve_labels(subgroup, params) } + + it { self.instance_exec(&test) } + end + end + end + + def resolve_labels(parent, args = {}, context = { current_user: current_user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/labels_resolver_spec.rb b/spec/graphql/resolvers/labels_resolver_spec.rb new file mode 100644 index 00000000000..3d027a6c8d5 --- /dev/null +++ b/spec/graphql/resolvers/labels_resolver_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::LabelsResolver do + include GraphqlHelpers + + using RSpec::Parameterized::TableSyntax + + let_it_be(:current_user) { create(:user) } + let_it_be(:group, reload: true) { create(:group, :private) } + let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) } + let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) } + let_it_be(:project, reload: true) { create(:project, :private, group: subgroup) } + let_it_be(:label1) { create(:label, project: project, name: 'project feature') } + let_it_be(:label2) { create(:label, project: project, name: 'new project feature') } + let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') } + let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') } + let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') } + let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') } + let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') } + let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'returns no labels' do + expect { resolve_labels(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with authorized user' do + it 'returns no labels' do + group.add_guest(current_user) + + expect { resolve_labels(project) }.not_to raise_error + end + end + + context 'without parent' do + it 'returns no labels' do + expect(resolve_labels(nil)).to eq(Label.none) + end + end + + context 'at project level' do + before_all do + group.add_developer(current_user) + end + + # because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false + # the `nil` value would be equivalent to passing in `false` so just check for `nil` option + where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do + nil | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) } + nil | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) } + nil | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + nil | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + + nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) } + nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) } + nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) } + nil | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) } + true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) } + true | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) } + true | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) } + true | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) } + end + + with_them do + let(:params) do + { + include_ancestor_groups: include_ancestor_groups, + include_descendant_groups: include_descendant_groups, + only_group_labels: only_group_labels, + search_term: search_term + } + end + + subject { resolve_labels(project, params) } + + it { self.instance_exec(&test) } + end + end + end + + def resolve_labels(parent, args = {}, context = { current_user: current_user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index de19e8b602a..bba702ba3e9 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -38,5 +38,7 @@ RSpec.describe GitlabSchema.types['Group'] do it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) } end - it_behaves_like 'a GraphQL type with labels' + it_behaves_like 'a GraphQL type with labels' do + let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] } + end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 95c835773e1..9579ef8b99b 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -332,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) } end - it_behaves_like 'a GraphQL type with labels' + it_behaves_like 'a GraphQL type with labels' do + let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups] } + end describe 'jira_imports' do subject { resolve_field(:jira_imports, project) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb index 3616461d94f..cd868a57bbc 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do %w(Template-1 Template-2).each do |expected_template| expect(Gitlab::UsageDataCounters::CiTemplateUniqueCounter).to( receive(:track_unique_project_event) - .with(project_id: project.id, template: expected_template) + .with(project_id: project.id, template: expected_template, config_source: pipeline.config_source) ) end diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index fa01d4e48df..20a8f2f6a41 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -160,6 +160,47 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do end end end + + context 'when resolving runs into a timeout' do + let(:import_url) { 'http://example.com' } + + subject { described_class.validate!(import_url, dns_rebind_protection: dns_rebind_protection) } + + before do + skip 'timeout is not available' unless timeout_available? + + stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') + stub_const("#{described_class}::GETADDRINFO_TIMEOUT_SECONDS", 0) + end + + context 'with dns rebinding enabled' do + let(:dns_rebind_protection) { true } + + it 'raises an error due to DNS timeout' do + expect { subject }.to raise_error(described_class::BlockedUrlError) + end + end + + context 'with dns rebinding disabled' do + let(:dns_rebind_protection) { false } + + it_behaves_like 'validates URI and hostname' do + let(:expected_uri) { import_url } + let(:expected_hostname) { nil } + end + end + + # Detect whether the timeout option is available. + # + # See https://bugs.ruby-lang.org/issues/15553 + def timeout_available? + Addrinfo.getaddrinfo('localhost', nil, timeout: 0) + + false + rescue SocketError + true + end + end end describe '#blocked_url?' do diff --git a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb index 4b07f9143b5..b1d5d106082 100644 --- a/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/ci_template_unique_counter_spec.rb @@ -3,28 +3,88 @@ require 'spec_helper' RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter do - let(:project_id) { 1 } - describe '.track_unique_project_event' do - described_class::TEMPLATE_TO_EVENT.keys.each do |template| - context "when given template #{template}" do - it_behaves_like 'tracking unique hll events' do - subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template) } + using RSpec::Parameterized::TableSyntax + + where(:template, :config_source, :expected_event) do + # Implicit Auto DevOps usage + 'Auto-DevOps.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops' + 'Jobs/Build.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_build' + 'Jobs/Deploy.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_deploy' + 'Security/SAST.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_sast' + 'Security/Secret-Detection.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_secret_detection' + # Explicit include:template usage + '5-Minute-Production-App.gitlab-ci.yml' | :repository_source | 'p_ci_templates_5_min_production_app' + 'Auto-DevOps.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops' + 'AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_cf_deploy_ec2' + 'AWS/Deploy-ECS.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_deploy_ecs' + 'Jobs/Build.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_build' + 'Jobs/Deploy.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy' + 'Jobs/Deploy.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy_latest' + 'Security/SAST.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_sast' + 'Security/Secret-Detection.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_secret_detection' + 'Terraform/Base.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_terraform_base_latest' + end + + with_them do + it_behaves_like 'tracking unique hll events' do + subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) } + + let(:project_id) { 1 } + let(:target_id) { expected_event } + let(:expected_type) { instance_of(Integer) } + end + end + + context 'known_events coverage tests' do + let(:project_id) { 1 } + let(:config_source) { :repository_source } - let(:target_id) { "p_ci_templates_#{described_class::TEMPLATE_TO_EVENT[template]}" } - let(:expected_type) { instance_of(Integer) } + # These tests help guard against missing "explicit" events in known_events/ci_templates.yml + context 'explicit include:template events' do + described_class::TEMPLATE_TO_EVENT.keys.each do |template| + it "does not raise error for #{template}" do + expect do + described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) + end.not_to raise_error + end + end + end + + # This test is to help guard against missing "implicit" events in known_events/ci_templates.yml + it 'does not raise error for any template in an implicit Auto DevOps pipeline' do + project = create(:project, :auto_devops) + pipeline = double(project: project) + command = double + result = Gitlab::Ci::YamlProcessor.new( + Gitlab::Ci::Pipeline::Chain::Config::Content::AutoDevops.new(pipeline, command).content, + project: project, + user: double, + sha: double + ).execute + + config_source = :auto_devops_source + + result.included_templates.each do |template| + expect do + described_class.track_unique_project_event(project_id: project.id, template: template, config_source: config_source) + end.not_to raise_error end end end - it 'does not track templates outside of TEMPLATE_TO_EVENT' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to( - receive(:track_event) - ) + context 'templates outside of TEMPLATE_TO_EVENT' do + let(:project_id) { 1 } + let(:config_source) { :repository_source } + Dir.glob(File.join('lib', 'gitlab', 'ci', 'templates', '**'), base: Rails.root) do |template| next if described_class::TEMPLATE_TO_EVENT.key?(template) - described_class.track_unique_project_event(project_id: 1, template: template) + it "does not track #{template}" do + expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to(receive(:track_event)) + + described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) + end end end end diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb index 1795b0a9ac3..52c708e77a5 100644 --- a/spec/support/helpers/dns_helpers.rb +++ b/spec/support/helpers/dns_helpers.rb @@ -12,19 +12,38 @@ module DnsHelpers end def stub_all_dns! - allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM).and_return([]) - allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM, anything, anything).and_return([]) + allow(Addrinfo).to receive(:getaddrinfo).and_return([]) end def stub_invalid_dns! - allow(Addrinfo).to receive(:getaddrinfo).with(/\Afoobar\.\w|(\d{1,3}\.){4,}\d{1,3}\z/i, anything, nil, :STREAM) do - raise SocketError.new("getaddrinfo: Name or service not known") - end + invalid_addresses = %r{ + \A + (?: + foobar\.\w | + (?:\d{1,3}\.){4,}\d{1,3} + ) + \z + }ix + + allow(Addrinfo).to receive(:getaddrinfo) + .with(invalid_addresses, any_args) + .and_raise(SocketError, 'getaddrinfo: Name or service not known') end def permit_local_dns! - local_addresses = /\A(127|10)\.0\.0\.\d{1,3}|(192\.168|172\.16)\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost\z/i - allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original - allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original + local_addresses = %r{ + \A + (?: + (?:127|10)\.0\.0\.\d{1,3} | + (?:192\.168|172\.16)\.\d{1,3}\.\d{1,3} | + 0\.0\.0\.0 | + localhost + ) + \z + }ix + + allow(Addrinfo).to receive(:getaddrinfo) + .with(local_addresses, any_args) + .and_call_original end end diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb index 473f07dd413..81872b1c908 100644 --- a/spec/support/helpers/stub_requests.rb +++ b/spec/support/helpers/stub_requests.rb @@ -18,14 +18,13 @@ module StubRequests end def stub_dns(url, ip_address:, port: 80) - url = parse_url(url) + url = URI(url) socket = Socket.sockaddr_in(port, ip_address) addr = Addrinfo.new(socket) - # See Gitlab::UrlBlocker allow(Addrinfo).to receive(:getaddrinfo) - .with(url.hostname, url.port, nil, :STREAM) - .and_return([addr]) + .with(url.hostname, url.port, any_args) + .and_return([addr]) end def stub_all_dns(url, ip_address:) @@ -34,22 +33,14 @@ module StubRequests socket = Socket.sockaddr_in(port, ip_address) addr = Addrinfo.new(socket) - # See Gitlab::UrlBlocker - allow(Addrinfo).to receive(:getaddrinfo).and_call_original allow(Addrinfo).to receive(:getaddrinfo) - .with(url.hostname, anything, nil, :STREAM) + .with(url.hostname, any_args) .and_return([addr]) end def stubbed_hostname(url, hostname: IP_ADDRESS_STUB) - url = parse_url(url) + url = URI(url) url.hostname = hostname url.to_s end - - private - - def parse_url(url) - url.is_a?(URI) ? url : URI(url) - end end diff --git a/spec/support/shared_examples/graphql/label_fields.rb b/spec/support/shared_examples/graphql/label_fields.rb index 0d09ab391f1..4159e4e03ab 100644 --- a/spec/support/shared_examples/graphql/label_fields.rb +++ b/spec/support/shared_examples/graphql/label_fields.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'a GraphQL type with labels' do subject { described_class.fields['labels'] } it { is_expected.to have_graphql_type(Types::LabelType.connection_type) } - it { is_expected.to have_graphql_arguments(:search_term) } + it { is_expected.to have_graphql_arguments(labels_resolver_arguments) } end end |