diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-31 15:08:32 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-31 15:08:32 +0000 |
| commit | 85ea3dd4f4855e99d9a69c8e609095199597195a (patch) | |
| tree | 46e74d30d0ef5cced04005b4a76c66ec18f6a749 /spec | |
| parent | 5820d448c17f93606afb52d878c00d84681764e0 (diff) | |
| download | gitlab-ce-85ea3dd4f4855e99d9a69c8e609095199597195a.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 436 insertions, 237 deletions
diff --git a/spec/components/diffs/stats_component_spec.rb b/spec/components/diffs/stats_component_spec.rb new file mode 100644 index 00000000000..2e5a5f2ca26 --- /dev/null +++ b/spec/components/diffs/stats_component_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Diffs::StatsComponent, type: :component do + include RepoHelpers + + subject(:component) do + described_class.new(diff_files: diff_files) + end + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:repository) { project.repository } + let_it_be(:commit) { project.commit(sample_commit.id) } + let_it_be(:diffs) { commit.raw_diffs } + let_it_be(:diff) { diffs.first } + let_it_be(:diff_refs) { commit.diff_refs } + let_it_be(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } + let_it_be(:diff_files) { [diff_file] } + + describe "rendered component" do + subject { rendered_component } + + let(:element) { page.find(".js-diff-stats-dropdown") } + + before do + render_inline component + end + + it { is_expected.to have_selector(".js-diff-stats-dropdown") } + + it "renders the data attributes" do + expect(element["data-changed"]).to eq("1") + expect(element["data-added"]).to eq("10") + expect(element["data-deleted"]).to eq("3") + + expect(Gitlab::Json.parse(element["data-files"])).to eq([{ + "href" => "##{Digest::SHA1.hexdigest(diff_file.file_path)}", + "title" => diff_file.new_path, + "name" => diff_file.file_path, + "path" => diff_file.file_path, + "icon" => "file-modified", + "iconColor" => "", + "added" => diff_file.added_lines, + "removed" => diff_file.removed_lines + }]) + end + end + + describe "#diff_file_path_text" do + it "returns full path by default" do + expect(subject.diff_file_path_text(diff_file)).to eq(diff_file.new_path) + end + + it "returns truncated path" do + expect(subject.diff_file_path_text(diff_file, max: 10)).to eq("...open.rb") + end + + it "returns the path if max is oddly small" do + expect(subject.diff_file_path_text(diff_file, max: 3)).to eq(diff_file.new_path) + end + + it "returns the path if max is oddly large" do + expect(subject.diff_file_path_text(diff_file, max: 100)).to eq(diff_file.new_path) + end + end +end diff --git a/spec/components/pajamas/alert_component_spec.rb b/spec/components/pajamas/alert_component_spec.rb new file mode 100644 index 00000000000..628d715ff64 --- /dev/null +++ b/spec/components/pajamas/alert_component_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do + context 'with content' do + before do + render_inline(described_class.new) { '_content_' } + end + + it 'has content' do + expect(rendered_component).to have_text('_content_') + end + end + + context 'with defaults' do + before do + render_inline described_class.new + end + + it 'does not set a title' do + expect(rendered_component).not_to have_selector('.gl-alert-title') + expect(rendered_component).to have_selector('.gl-alert-icon-no-title') + end + + it 'renders the default variant' do + expect(rendered_component).to have_selector('.gl-alert-info') + expect(rendered_component).to have_selector("[data-testid='information-o-icon']") + end + + it 'renders a dismiss button' do + expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close') + expect(rendered_component).to have_selector("[data-testid='close-icon']") + end + end + + context 'with custom options' do + context 'with simple options' do + context 'without dismissible content' do + before do + render_inline described_class.new( + title: '_title_', + dismissible: false, + alert_class: '_alert_class_', + alert_data: { + feature_id: '_feature_id_', + dismiss_endpoint: '_dismiss_endpoint_' + } + ) + end + + it 'sets the title' do + expect(rendered_component).to have_selector('.gl-alert-title') + expect(rendered_component).to have_content('_title_') + expect(rendered_component).not_to have_selector('.gl-alert-icon-no-title') + end + + it 'sets to not be dismissible' do + expect(rendered_component).not_to have_selector('.gl-dismiss-btn.js-close') + expect(rendered_component).not_to have_selector("[data-testid='close-icon']") + end + + it 'sets the alert_class' do + expect(rendered_component).to have_selector('._alert_class_') + end + + it 'sets the alert_data' do + expect(rendered_component).to have_selector('[data-feature-id="_feature_id_"][data-dismiss-endpoint="_dismiss_endpoint_"]') + end + end + end + + context 'with dismissible content' do + before do + render_inline described_class.new( + close_button_class: '_close_button_class_', + close_button_data: { + testid: '_close_button_testid_' + } + ) + end + + it 'renders a dismiss button and data' do + expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close._close_button_class_') + expect(rendered_component).to have_selector("[data-testid='close-icon']") + expect(rendered_component).to have_selector('[data-testid="_close_button_testid_"]') + end + end + + context 'with setting variant type' do + where(:variant) { [:warning, :success, :danger, :tip] } + + before do + render_inline described_class.new(variant: variant) + end + + with_them do + it 'renders the variant' do + expect(rendered_component).to have_selector(".gl-alert-#{variant}") + expect(rendered_component).to have_selector("[data-testid='#{described_class::ICONS[variant]}-icon']") + end + end + end + end +end diff --git a/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb b/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb index a655c742973..fc741d0f3f6 100644 --- a/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb +++ b/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb @@ -41,17 +41,5 @@ RSpec.describe Projects::Packages::InfrastructureRegistryController do it_behaves_like 'returning response status', :not_found end - - context 'with package file pending destruction' do - let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: terraform_module) } - - let(:terraform_module_package_file) { terraform_module.package_files.first } - - it 'does not return them' do - subject - - expect(assigns(:package_files)).to contain_exactly(terraform_module_package_file) - end - end end end diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index f781ba0827c..a15b6072e70 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -70,7 +70,7 @@ RSpec.describe 'Merge requests > User mass updates', :js do it 'updates merge request with assignee' do change_assignee(user.name) - expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user.name}, go to their profile." + expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user.name}" end end end diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 6f6f50b89bc..b96dfc00c11 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -2,11 +2,13 @@ import $ from 'jquery'; import { nextTick } from 'vue'; import '~/behaviors/markdown/render_gfm'; import { GlTooltip, GlModal } from '@gitlab/ui'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { stubComponent } from 'helpers/stub_component'; import { TEST_HOST } from 'helpers/test_constants'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import Description from '~/issues/show/components/description.vue'; +import { updateHistory } from '~/lib/utils/url_utility'; import TaskList from '~/task_list'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; @@ -17,6 +19,10 @@ import { } from '../mock_data/mock_data'; jest.mock('~/flash'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + updateHistory: jest.fn(), +})); jest.mock('~/task_list'); const showModal = jest.fn(); @@ -55,6 +61,8 @@ describe('Description component', () => { } beforeEach(() => { + setWindowLocation(TEST_HOST); + if (!document.querySelector('.issuable-meta')) { const metaData = document.createElement('div'); metaData.classList.add('issuable-meta'); @@ -285,48 +293,86 @@ describe('Description component', () => { describe('work items detail', () => { const findTaskLink = () => wrapper.find('a.gfm-issue'); - beforeEach(() => { - createComponent({ - props: { - descriptionHtml: descriptionHtmlWithTask, - }, - provide: { - glFeatures: { workItems: true }, - }, + describe('when opening and closing', () => { + beforeEach(() => { + createComponent({ + props: { + descriptionHtml: descriptionHtmlWithTask, + }, + provide: { + glFeatures: { workItems: true }, + }, + }); + return nextTick(); }); - return nextTick(); - }); - it('opens when task button is clicked', async () => { - expect(findWorkItemDetailModal().props('visible')).toBe(false); + it('opens when task button is clicked', async () => { + expect(findWorkItemDetailModal().props('visible')).toBe(false); - await findTaskLink().trigger('click'); + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(updateHistory).toHaveBeenCalledWith({ + url: `${TEST_HOST}/?work_item_id=2`, + replace: true, + }); + }); - it('closes from an open state', async () => { - await findTaskLink().trigger('click'); + it('closes from an open state', async () => { + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(findWorkItemDetailModal().props('visible')).toBe(true); - findWorkItemDetailModal().vm.$emit('close'); - await nextTick(); + findWorkItemDetailModal().vm.$emit('close'); + await nextTick(); - expect(findWorkItemDetailModal().props('visible')).toBe(false); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(false); + expect(updateHistory).toHaveBeenLastCalledWith({ + url: `${TEST_HOST}/`, + replace: true, + }); + }); - it('tracks when opened', async () => { - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + it('tracks when opened', async () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await findTaskLink().trigger('click'); + await findTaskLink().trigger('click'); - expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'viewed_work_item_from_modal', { - category: 'workItems:show', - label: 'work_item_view', - property: 'type_task', + expect(trackingSpy).toHaveBeenCalledWith( + 'workItems:show', + 'viewed_work_item_from_modal', + { + category: 'workItems:show', + label: 'work_item_view', + property: 'type_task', + }, + ); }); }); + + describe('when url query `work_item_id` exists', () => { + it.each` + behavior | workItemId | visible + ${'opens'} | ${'123'} | ${true} + ${'does not open'} | ${'123e'} | ${false} + ${'does not open'} | ${'12e3'} | ${false} + ${'does not open'} | ${'1e23'} | ${false} + ${'does not open'} | ${'x'} | ${false} + ${'does not open'} | ${'undefined'} | ${false} + `( + '$behavior when url contains `work_item_id=$workItemId`', + async ({ workItemId, visible }) => { + setWindowLocation(`?work_item_id=${workItemId}`); + + createComponent({ + props: { descriptionHtml: descriptionHtmlWithTask }, + provide: { glFeatures: { workItems: true } }, + }); + + expect(findWorkItemDetailModal().props('visible')).toBe(visible); + }, + ); + }); }); }); }); diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js index 175896c4ab0..97d1b077164 100644 --- a/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js +++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js @@ -5,7 +5,7 @@ import SignInLegacyButton from '~/jira_connect/subscriptions/components/sign_in_ import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue'; import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue'; import createStore from '~/jira_connect/subscriptions/store'; -import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT } from '../../../../../app/assets/javascripts/jira_connect/subscriptions/constants'; +import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT } from '~/jira_connect/subscriptions/constants'; jest.mock('~/jira_connect/subscriptions/utils'); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index edc25eb7e02..f8011860a49 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -41,7 +41,13 @@ import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.qu import { captureException } from '~/runner/sentry_utils'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data'; +import { + runnersData, + runnersCountData, + runnersDataPaginated, + onlineContactTimeoutSecs, + staleTimeoutSecs, +} from '../mock_data'; const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; const mockRunners = runnersData.data.runners.nodes; @@ -94,6 +100,8 @@ describe('AdminRunnersApp', () => { }, provide: { localMutations, + onlineContactTimeoutSecs, + staleTimeoutSecs, ...provide, }, ...options, diff --git a/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap new file mode 100644 index 00000000000..80a04401760 --- /dev/null +++ b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunnerStatusPopover renders complete text 1`] = `"Never contacted: Runner has never contacted GitLab (when you register a runner, use gitlab-runner run to bring it online) Online: Runner has contacted GitLab within the last 2 hours Offline: Runner has not contacted GitLab in more than 2 hours Stale: Runner has not contacted GitLab in more than 2 months"`; diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js index fab94771990..872394430ae 100644 --- a/spec/frontend/runner/components/runner_list_spec.js +++ b/spec/frontend/runner/components/runner_list_spec.js @@ -6,7 +6,8 @@ import { } from 'helpers/vue_test_utils_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import RunnerList from '~/runner/components/runner_list.vue'; -import { runnersData } from '../mock_data'; +import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue'; +import { runnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data'; const mockRunners = runnersData.data.runners.nodes; const mockActiveRunnersCount = mockRunners.length; @@ -28,21 +29,34 @@ describe('RunnerList', () => { activeRunnersCount: mockActiveRunnersCount, ...props, }, + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + }, ...options, }); }; - beforeEach(() => { - createComponent({}, mountExtended); - }); - afterEach(() => { wrapper.destroy(); }); it('Displays headers', () => { + createComponent( + { + stubs: { + RunnerStatusPopover: { + template: '<div/>', + }, + }, + }, + mountExtended, + ); + const headerLabels = findHeaders().wrappers.map((w) => w.text()); + expect(findHeaders().at(0).findComponent(RunnerStatusPopover).exists()).toBe(true); + expect(headerLabels).toEqual([ 'Status', 'Runner', @@ -61,6 +75,8 @@ describe('RunnerList', () => { }); it('Displays a list of runners', () => { + createComponent({}, mountExtended); + expect(findRows()).toHaveLength(4); expect(findSkeletonLoader().exists()).toBe(false); @@ -69,6 +85,8 @@ describe('RunnerList', () => { it('Displays details of a runner', () => { const { id, description, version, shortSha } = mockRunners[0]; + createComponent({}, mountExtended); + // Badges expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText( 'never contacted paused', @@ -183,6 +201,8 @@ describe('RunnerList', () => { const { id, shortSha } = mockRunners[0]; const numericId = getIdFromGraphQLId(id); + createComponent({}, mountExtended); + expect(findCell({ fieldKey: 'summary' }).text()).toContain(`#${numericId} (${shortSha})`); }); diff --git a/spec/frontend/runner/components/runner_status_popover_spec.js b/spec/frontend/runner/components/runner_status_popover_spec.js new file mode 100644 index 00000000000..789283d1245 --- /dev/null +++ b/spec/frontend/runner/components/runner_status_popover_spec.js @@ -0,0 +1,36 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; +import { onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data'; + +describe('RunnerStatusPopover', () => { + let wrapper; + + const createComponent = ({ provide = {} } = {}) => { + wrapper = shallowMountExtended(RunnerStatusPopover, { + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + ...provide, + }, + stubs: { + GlSprintf, + }, + }); + }; + + const findHelpPopover = () => wrapper.findComponent(HelpPopover); + + it('renders popoover', () => { + createComponent(); + + expect(findHelpPopover().exists()).toBe(true); + }); + + it('renders complete text', () => { + createComponent(); + + expect(findHelpPopover().text()).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 6d7ecc4506a..706f46a7c56 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -38,7 +38,13 @@ import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; import { captureException } from '~/runner/sentry_utils'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data'; +import { + groupRunnersData, + groupRunnersDataPaginated, + groupRunnersCountData, + onlineContactTimeoutSecs, + staleTimeoutSecs, +} from '../mock_data'; Vue.use(VueApollo); Vue.use(GlToast); @@ -90,6 +96,10 @@ describe('GroupRunnersApp', () => { groupRunnersLimitedCount: mockGroupRunnersLimitedCount, ...props, }, + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + }, }); }; diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 49c25039719..fbe8926124c 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -14,6 +14,10 @@ import runnerWithGroupData from 'test_fixtures/graphql/runner/details/runner.que import runnerProjectsData from 'test_fixtures/graphql/runner/details/runner_projects.query.graphql.json'; import runnerJobsData from 'test_fixtures/graphql/runner/details/runner_jobs.query.graphql.json'; +// Other mock data +export const onlineContactTimeoutSecs = 2 * 60 * 60; +export const staleTimeoutSecs = 5259492; // Ruby's `2.months` + export { runnersData, runnersCountData, diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index dd1804e2078..0046d481282 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -86,7 +86,9 @@ RSpec.describe Ci::RunnersHelper do it 'returns the data in format' do expect(helper.admin_runners_data_attributes).to eq({ runner_install_help_page: 'https://docs.gitlab.com/runner/install/', - registration_token: Gitlab::CurrentSettings.runners_registration_token + registration_token: Gitlab::CurrentSettings.runners_registration_token, + online_contact_timeout_secs: 7200, + stale_timeout_secs: 7889238 }) end end @@ -128,12 +130,14 @@ RSpec.describe Ci::RunnersHelper do let(:group) { create(:group) } it 'returns group data to render a runner list' do - data = helper.group_runners_data_attributes(group) - - expect(data[:registration_token]).to eq(group.runners_token) - expect(data[:group_id]).to eq(group.id) - expect(data[:group_full_path]).to eq(group.full_path) - expect(data[:runner_install_help_page]).to eq('https://docs.gitlab.com/runner/install/') + expect(helper.group_runners_data_attributes(group)).to eq({ + registration_token: group.runners_token, + group_id: group.id, + group_full_path: group.full_path, + runner_install_help_page: 'https://docs.gitlab.com/runner/install/', + online_contact_timeout_secs: 7200, + stale_timeout_secs: 7889238 + }) end end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index ccc2db00236..84e702cd6a9 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -425,16 +425,6 @@ RSpec.describe DiffHelper do end end - describe '#diff_file_path_text' do - it 'returns full path by default' do - expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path) - end - - it 'returns truncated path' do - expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb") - end - end - describe "#collapsed_diff_url" do let(:params) do { diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index 90ca5430526..3fa535dd800 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -8,18 +8,22 @@ RSpec.describe Gitlab::DataBuilder::Note do let(:data) { described_class.build(note, user) } let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors - before do - expect(data).to have_key(:object_attributes) - expect(data[:object_attributes]).to have_key(:url) - expect(data[:object_attributes][:url]) - .to eq(Gitlab::UrlBuilder.build(note)) - expect(data[:object_kind]).to eq('note') - expect(data[:user]).to eq(user.hook_attrs) + shared_examples 'includes general data' do + specify do + expect(data).to have_key(:object_attributes) + expect(data[:object_attributes]).to have_key(:url) + expect(data[:object_attributes][:url]) + .to eq(Gitlab::UrlBuilder.build(note)) + expect(data[:object_kind]).to eq('note') + expect(data[:user]).to eq(user.hook_attrs) + end end describe 'When asking for a note on commit' do let(:note) { create(:note_on_commit, project: project) } + it_behaves_like 'includes general data' + it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end @@ -31,6 +35,8 @@ RSpec.describe Gitlab::DataBuilder::Note do describe 'When asking for a note on commit diff' do let(:note) { create(:diff_note_on_commit, project: project) } + it_behaves_like 'includes general data' + it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end @@ -51,22 +57,21 @@ RSpec.describe Gitlab::DataBuilder::Note do create(:note_on_issue, noteable: issue, project: project) end + it_behaves_like 'includes general data' + it 'returns the note and issue-specific data' do - without_timestamps = lambda { |label| label.except('created_at', 'updated_at') } - hook_attrs = issue.reload.hook_attrs + expect_next_instance_of(Gitlab::HookData::IssueBuilder) do |issue_data_builder| + expect(issue_data_builder).to receive(:build).and_return('Issue data') + end - expect(data).to have_key(:issue) - expect(data[:issue].except('updated_at', 'labels')) - .to eq(hook_attrs.except('updated_at', 'labels')) - expect(data[:issue]['updated_at']) - .to be >= hook_attrs['updated_at'] - expect(data[:issue]['labels'].map(&without_timestamps)) - .to eq(hook_attrs['labels'].map(&without_timestamps)) + expect(data[:issue]).to eq('Issue data') end context 'with confidential issue' do let(:issue) { create(:issue, project: project, confidential: true) } + it_behaves_like 'includes general data' + it 'sets event_type to confidential_note' do expect(data[:event_type]).to eq('confidential_note') end @@ -77,10 +82,12 @@ RSpec.describe Gitlab::DataBuilder::Note do end describe 'When asking for a note on merge request' do + let(:label) { create(:label, project: project) } let(:merge_request) do - create(:merge_request, created_at: fixed_time, + create(:labeled_merge_request, created_at: fixed_time, updated_at: fixed_time, - source_project: project) + source_project: project, + labels: [label]) end let(:note) do @@ -88,12 +95,14 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end - it 'returns the note and merge request data' do - expect(data).to have_key(:merge_request) - expect(data[:merge_request].except('updated_at')) - .to eq(merge_request.reload.hook_attrs.except('updated_at')) - expect(data[:merge_request]['updated_at']) - .to be >= merge_request.hook_attrs['updated_at'] + it_behaves_like 'includes general data' + + it 'returns the merge request data' do + expect_next_instance_of(Gitlab::HookData::MergeRequestBuilder) do |mr_data_builder| + expect(mr_data_builder).to receive(:build).and_return('MR data') + end + + expect(data[:merge_request]).to eq('MR data') end include_examples 'project hook data' @@ -101,9 +110,11 @@ RSpec.describe Gitlab::DataBuilder::Note do end describe 'When asking for a note on merge request diff' do + let(:label) { create(:label, project: project) } let(:merge_request) do - create(:merge_request, created_at: fixed_time, updated_at: fixed_time, - source_project: project) + create(:labeled_merge_request, created_at: fixed_time, updated_at: fixed_time, + source_project: project, + labels: [label]) end let(:note) do @@ -111,12 +122,14 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end - it 'returns the note and merge request diff data' do - expect(data).to have_key(:merge_request) - expect(data[:merge_request].except('updated_at')) - .to eq(merge_request.reload.hook_attrs.except('updated_at')) - expect(data[:merge_request]['updated_at']) - .to be >= merge_request.hook_attrs['updated_at'] + it_behaves_like 'includes general data' + + it 'returns the merge request data' do + expect_next_instance_of(Gitlab::HookData::MergeRequestBuilder) do |mr_data_builder| + expect(mr_data_builder).to receive(:build).and_return('MR data') + end + + expect(data[:merge_request]).to eq('MR data') end include_examples 'project hook data' @@ -134,6 +147,8 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end + it_behaves_like 'includes general data' + it 'returns the note and project snippet data' do expect(data).to have_key(:snippet) expect(data[:snippet].except('updated_at')) diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb index f5ee8eba8bc..676396697fb 100644 --- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do let_it_be(:user) { create(:user) } # This shared example requires a `builder` and `user` variable - shared_examples 'issuable hook data' do |kind| + shared_examples 'issuable hook data' do |kind, hook_data_issuable_builder_class| let(:data) { builder.build(user: user) } include_examples 'project hook data' do @@ -20,7 +20,7 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do expect(data[:object_kind]).to eq(kind) expect(data[:user]).to eq(user.hook_attrs) expect(data[:project]).to eq(builder.issuable.project.hook_attrs) - expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:object_attributes]).to eq(hook_data_issuable_builder_class.new(issuable).build) expect(data[:changes]).to eq({}) expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) end @@ -95,12 +95,12 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do end describe '#build' do - it_behaves_like 'issuable hook data', 'issue' do + it_behaves_like 'issuable hook data', 'issue', Gitlab::HookData::IssueBuilder do let(:issuable) { create(:issue, description: 'A description') } let(:builder) { described_class.new(issuable) } end - it_behaves_like 'issuable hook data', 'merge_request' do + it_behaves_like 'issuable hook data', 'merge_request', Gitlab::HookData::MergeRequestBuilder do let(:issuable) { create(:merge_request, description: 'A description') } let(:builder) { described_class.new(issuable) } end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index ddd681f75f0..771fc0218e2 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -62,6 +62,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do expect(data).to include(:human_time_estimate) expect(data).to include(:human_total_time_spent) expect(data).to include(:human_time_change) + expect(data).to include(:labels) end context 'when the MR has an image in the description' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index e3c0e3a7a2b..b38135fc0b2 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -625,6 +625,16 @@ RSpec.describe Issuable do end end + describe "#labels_hook_attrs" do + let(:project) { create(:project) } + let(:label) { create(:label) } + let(:issue) { create(:labeled_issue, project: project, labels: [label]) } + + it "returns a list of label hook attributes" do + expect(issue.labels_hook_attrs).to match_array([label.hook_attrs]) + end + end + describe '.labels_hash' do let(:feature_label) { create(:label, title: 'Feature') } let(:second_label) { create(:label, title: 'Second Label') } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 61ad9dc26be..fe09dadd0db 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1172,18 +1172,6 @@ RSpec.describe Issue do end end - describe '#hook_attrs' do - it 'delegates to Gitlab::HookData::IssueBuilder#build' do - builder = double - - expect(Gitlab::HookData::IssueBuilder) - .to receive(:new).with(subject).and_return(builder) - expect(builder).to receive(:build) - - subject.hook_attrs - end - end - describe '#check_for_spam?' do let_it_be(:support_bot) { ::User.support_bot } @@ -1332,15 +1320,6 @@ RSpec.describe Issue do subject { create(:issue, updated_at: 1.hour.ago) } end - describe "#labels_hook_attrs" do - let(:label) { create(:label) } - let(:issue) { create(:labeled_issue, project: reusable_project, labels: [label]) } - - it "returns a list of label hook attributes" do - expect(issue.labels_hook_attrs).to eq([label.hook_attrs]) - end - end - context "relative positioning" do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 0d15851e583..9d734e85668 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1757,18 +1757,6 @@ RSpec.describe MergeRequest, factory_default: :keep do end end - describe '#hook_attrs' do - it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do - builder = double - - expect(Gitlab::HookData::MergeRequestBuilder) - .to receive(:new).with(subject).and_return(builder) - expect(builder).to receive(:build) - - subject.hook_attrs - end - end - describe '#diverged_commits_count' do let(:project) { create(:project, :repository) } let(:forked_project) { fork_project(project, nil, repository: true) } diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index 4ad2446f8d0..6d84b2c6931 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -89,19 +89,6 @@ RSpec.describe ProjectImportState, type: :model do import_state.mark_as_failed(error_message) end.to change { project.reload.import_data }.from(import_data).to(nil) end - - context 'when remove_import_data_on_failure feature flag is disabled' do - it 'removes project import data' do - stub_feature_flags(remove_import_data_on_failure: false) - - project = create(:project, import_data: ProjectImportData.new(data: { 'test' => 'some data' })) - import_state = create(:import_state, :started, project: project) - - expect do - import_state.mark_as_failed(error_message) - end.not_to change { project.reload.import_data } - end - end end describe '#human_status_name' do diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb deleted file mode 100644 index 13ffc1b7f87..00000000000 --- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# This shared example requires a `builder` and `user` variable -RSpec.shared_examples 'issuable hook data' do |kind| - let(:data) { builder.build(user: user) } - - include_examples 'project hook data' do - let(:project) { builder.issuable.project } - end - - include_examples 'deprecated repository hook data' - - context "with a #{kind}" do - it 'contains issuable data' do - expect(data[:object_kind]).to eq(kind) - expect(data[:user]).to eq(user.hook_attrs) - expect(data[:project]).to eq(builder.issuable.project.hook_attrs) - expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) - expect(data[:changes]).to eq({}) - expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) - end - - it 'does not contain certain keys' do - expect(data).not_to have_key(:assignees) - expect(data).not_to have_key(:assignee) - end - - describe 'changes are given' do - let(:changes) do - { - cached_markdown_version: %w[foo bar], - description: ['A description', 'A cool description'], - description_html: %w[foo bar], - in_progress_merge_commit_sha: %w[foo bar], - lock_version: %w[foo bar], - merge_jid: %w[foo bar], - title: ['A title', 'Hello World'], - title_html: %w[foo bar] - } - end - - let(:data) { builder.build(user: user, changes: changes) } - - it 'populates the :changes hash' do - expect(data[:changes]).to match(hash_including({ - title: { previous: 'A title', current: 'Hello World' }, - description: { previous: 'A description', current: 'A cool description' } - })) - end - - it 'does not contain certain keys' do - expect(data[:changes]).not_to have_key('cached_markdown_version') - expect(data[:changes]).not_to have_key('description_html') - expect(data[:changes]).not_to have_key('lock_version') - expect(data[:changes]).not_to have_key('title_html') - expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') - expect(data[:changes]).not_to have_key('merge_jid') - end - end - end -end diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 95471203983..7677e5d8cb2 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -154,6 +154,30 @@ RSpec.shared_examples 'logs an auth warning' do |requested_actions| end end +RSpec.shared_examples 'allowed to delete container repository images' do + let(:authentication_abilities) do + [:admin_container_image] + end + + it_behaves_like 'a valid token' + + context 'allow to delete images' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:*"] } + end + + it_behaves_like 'a deletable' + end + + context 'allow to delete images since registry 2.7' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:delete"] } + end + + it_behaves_like 'a deletable since registry 2.7' + end +end + RSpec.shared_examples 'a container registry auth service' do include_context 'container registry auth service context' @@ -544,38 +568,14 @@ RSpec.shared_examples 'a container registry auth service' do end context 'delete authorized as maintainer' do - let_it_be(:current_project) { create(:project) } + let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } - let(:authentication_abilities) do - [:admin_container_image] - end - before_all do - current_project.add_maintainer(current_user) - end - - it_behaves_like 'a valid token' - - context 'allow to delete images' do - let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:*"] } - end - - it_behaves_like 'a deletable' do - let(:project) { current_project } - end + project.add_maintainer(current_user) end - context 'allow to delete images since registry 2.7' do - let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:delete"] } - end - - it_behaves_like 'a deletable since registry 2.7' do - let(:project) { current_project } - end - end + it_behaves_like 'allowed to delete container repository images' end context 'build authorized as user' do |
