diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-11 21:10:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-11 21:10:57 +0300 |
commit | 7eeb03ce0e64c30df91665524e543fe4e611c410 (patch) | |
tree | 07cbca6dd30e8c358bca54a0494f4716ad35cd6f /spec | |
parent | d83a3edd4416e93f2815815c1be4ee0a2755a3c5 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 277 insertions, 35 deletions
diff --git a/spec/fixtures/api/schemas/public_api/v4/project_hook.json b/spec/fixtures/api/schemas/public_api/v4/project_hook.json index b89f5af8078..c42a4cad712 100644 --- a/spec/fixtures/api/schemas/public_api/v4/project_hook.json +++ b/spec/fixtures/api/schemas/public_api/v4/project_hook.json @@ -22,9 +22,11 @@ "releases_events", "alert_status", "disabled_until", - "url_variables", "emoji_events" ], + "optional": [ + "url_variables" + ], "properties": { "id": { "type": "integer" diff --git a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js index ba77d90f4e2..d44886a4f95 100644 --- a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js +++ b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js @@ -2,7 +2,7 @@ import { GlLoadingIcon, GlTable, GlLink, GlPagination, GlModal, GlFormCheckbox } import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import getJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import waitForPromises from 'helpers/wait_for_promises'; import JobArtifactsTable from '~/ci/artifacts/components/job_artifacts_table.vue'; import ArtifactsTableRowDetails from '~/ci/artifacts/components/artifacts_table_row_details.vue'; diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js index 8b3b051f8ca..6af9daabea0 100644 --- a/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js +++ b/spec/frontend/ci/catalog/components/details/ci_resource_header_spec.js @@ -3,7 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import CiResourceHeader from '~/ci/catalog/components/details/ci_resource_header.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import CiResourceAbout from '~/ci/catalog/components/details/ci_resource_about.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import { catalogSharedDataMock, catalogAdditionalDetailsMock } from '../../mock'; describe('CiResourceHeader', () => { diff --git a/spec/frontend/ci/common/pipelines_table_spec.js b/spec/frontend/ci/common/pipelines_table_spec.js index f6d3121109f..ca07e0ab8c8 100644 --- a/spec/frontend/ci/common/pipelines_table_spec.js +++ b/spec/frontend/ci/common/pipelines_table_spec.js @@ -16,7 +16,7 @@ import { TRACKING_CATEGORIES, } from '~/ci/constants'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; describe('Pipelines Table', () => { let wrapper; diff --git a/spec/frontend/ci/job_details/components/job_header_spec.js b/spec/frontend/ci/job_details/components/job_header_spec.js index d12267807ac..0b98d5fa935 100644 --- a/spec/frontend/ci/job_details/components/job_header_spec.js +++ b/spec/frontend/ci/job_details/components/job_header_spec.js @@ -1,7 +1,7 @@ import { GlButton, GlAvatarLink, GlTooltip } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import JobHeader from '~/ci/job_details/components/job_header.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; diff --git a/spec/frontend/ci/job_details/components/sidebar/job_container_item_spec.js b/spec/frontend/ci/job_details/components/sidebar/job_container_item_spec.js index 0eabaefd5de..697235dbe54 100644 --- a/spec/frontend/ci/job_details/components/sidebar/job_container_item_spec.js +++ b/spec/frontend/ci/job_details/components/sidebar/job_container_item_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import delayedJobFixture from 'test_fixtures/jobs/delayed.json'; import JobContainerItem from '~/ci/job_details/components/sidebar/job_container_item.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import job from 'jest/ci/jobs_mock_data'; describe('JobContainerItem', () => { diff --git a/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js b/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js index 54c5a73f757..a629c1c185a 100644 --- a/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js +++ b/spec/frontend/ci/job_details/components/sidebar/stages_dropdown_spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { Mousetrap } from '~/lib/mousetrap'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import StagesDropdown from '~/ci/job_details/components/sidebar/stages_dropdown.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import * as copyToClipboard from '~/behaviors/copy_to_clipboard'; import { mockPipelineWithoutRef, diff --git a/spec/frontend/ci/jobs_page/components/jobs_table_spec.js b/spec/frontend/ci/jobs_page/components/jobs_table_spec.js index d14afe7dd3e..a865b7a0c0c 100644 --- a/spec/frontend/ci/jobs_page/components/jobs_table_spec.js +++ b/spec/frontend/ci/jobs_page/components/jobs_table_spec.js @@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import JobsTable from '~/ci/jobs_page/components/jobs_table.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import { DEFAULT_FIELDS_ADMIN } from '~/ci/admin/jobs_table/constants'; import ProjectCell from '~/ci/admin/jobs_table/components/cells/project_cell.vue'; import RunnerCell from '~/ci/admin/jobs_table/components/cells/runner_cell.vue'; diff --git a/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js b/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js index 10db7f398fe..432775d469c 100644 --- a/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js +++ b/spec/frontend/ci/pipeline_details/graph/components/job_item_spec.js @@ -5,7 +5,7 @@ import JobItem from '~/ci/pipeline_details/graph/components/job_item.vue'; import axios from '~/lib/utils/axios_utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import ActionComponent from '~/ci/common/private/job_action_component.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { diff --git a/spec/frontend/ci/pipeline_details/graph/components/job_name_component_spec.js b/spec/frontend/ci/pipeline_details/graph/components/job_name_component_spec.js index 1da85ad9f78..b84ca77081a 100644 --- a/spec/frontend/ci/pipeline_details/graph/components/job_name_component_spec.js +++ b/spec/frontend/ci/pipeline_details/graph/components/job_name_component_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import jobNameComponent from '~/ci/common/private/job_name_component.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; describe('job name component', () => { let wrapper; diff --git a/spec/frontend/ci/pipeline_details/graph/components/linked_pipeline_spec.js b/spec/frontend/ci/pipeline_details/graph/components/linked_pipeline_spec.js index 72be51575d7..e6f89910a97 100644 --- a/spec/frontend/ci/pipeline_details/graph/components/linked_pipeline_spec.js +++ b/spec/frontend/ci/pipeline_details/graph/components/linked_pipeline_spec.js @@ -10,7 +10,7 @@ import { ACTION_FAILURE, UPSTREAM, DOWNSTREAM } from '~/ci/pipeline_details/grap import LinkedPipelineComponent from '~/ci/pipeline_details/graph/components/linked_pipeline.vue'; import CancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql'; import RetryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import mockPipeline from './linked_pipelines_mock_data'; describe('Linked pipeline', () => { diff --git a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js index ac118e07fa3..86b8c416a07 100644 --- a/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js +++ b/spec/frontend/ci/pipeline_details/header/pipeline_details_header_spec.js @@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import PipelineDetailsHeader from '~/ci/pipeline_details/header/pipeline_details_header.vue'; import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '~/ci/constants'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import cancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql'; import deletePipelineMutation from '~/ci/pipeline_details/graphql/mutations/delete_pipeline.mutation.graphql'; import retryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql'; diff --git a/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js b/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js index 87df7676bf1..95fa82adc9e 100644 --- a/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js +++ b/spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_stage_spec.js @@ -2,7 +2,7 @@ import { GlDropdown } from '@gitlab/ui'; import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import LegacyPipelineStage from '~/ci/pipeline_mini_graph/legacy_pipeline_stage.vue'; diff --git a/spec/frontend/ci/pipeline_mini_graph/linked_pipelines_mini_list_spec.js b/spec/frontend/ci/pipeline_mini_graph/linked_pipelines_mini_list_spec.js index 55ce3c79039..4f0bf3767cd 100644 --- a/spec/frontend/ci/pipeline_mini_graph/linked_pipelines_mini_list_spec.js +++ b/spec/frontend/ci/pipeline_mini_graph/linked_pipelines_mini_list_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import LinkedPipelinesMiniList from '~/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue'; import mockData from './linked_pipelines_mock_data'; diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js index b79e7c6e251..b79662e7a89 100644 --- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js @@ -1,5 +1,5 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import PipelineScheduleLastPipeline from '~/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline.vue'; import { mockPipelineScheduleNodes } from '../../../mock_data'; diff --git a/spec/frontend/commit/commit_pipeline_status_spec.js b/spec/frontend/commit/commit_pipeline_status_spec.js index 08a7ec17785..6d407ed886a 100644 --- a/spec/frontend/commit/commit_pipeline_status_spec.js +++ b/spec/frontend/commit/commit_pipeline_status_spec.js @@ -6,7 +6,7 @@ import fixture from 'test_fixtures/pipelines/pipelines.json'; import { createAlert } from '~/alert'; import Poll from '~/lib/utils/poll'; import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; jest.mock('~/lib/utils/poll'); jest.mock('visibilityjs'); diff --git a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js index 008a1b2c068..37ce234c61c 100644 --- a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js +++ b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js @@ -5,7 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; import CommitBoxPipelineStatus from '~/projects/commit_box/info/components/commit_box_pipeline_status.vue'; import { COMMIT_BOX_POLL_INTERVAL, diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js index 9c11ae9334b..9d8b3b1d32a 100644 --- a/spec/frontend/ide/components/pipelines/list_spec.js +++ b/spec/frontend/ide/components/pipelines/list_spec.js @@ -8,7 +8,7 @@ import JobsList from '~/ide/components/jobs/list.vue'; import List from '~/ide/components/pipelines/list.vue'; import EmptyState from '~/ide/components/pipelines/empty_state.vue'; import IDEServices from '~/ide/services'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; Vue.use(Vuex); diff --git a/spec/frontend/issuable/popover/components/mr_popover_spec.js b/spec/frontend/issuable/popover/components/mr_popover_spec.js index 4ed783da853..80b04c05524 100644 --- a/spec/frontend/issuable/popover/components/mr_popover_spec.js +++ b/spec/frontend/issuable/popover/components/mr_popover_spec.js @@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import MRPopover from '~/issuable/popover/components/mr_popover.vue'; import mergeRequestQuery from '~/issuable/popover/queries/merge_request.query.graphql'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; describe('MR Popover', () => { let wrapper; diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon/ci_icon_spec.js index cbb725bf9e6..792470c8e89 100644 --- a/spec/frontend/vue_shared/components/ci_icon_spec.js +++ b/spec/frontend/vue_shared/components/ci_icon/ci_icon_spec.js @@ -1,6 +1,6 @@ import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue'; const mockStatus = { group: 'success', diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js index 810269257b6..e2c3fc89525 100644 --- a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js @@ -152,7 +152,8 @@ describe('Diff Stats Dropdown', () => { }); it('focuses the first item when pressing the down key within the search box', () => { - const spy = jest.spyOn(wrapper.vm, 'focusFirstItem'); + const { element } = wrapper.find('.gl-new-dropdown-item'); + const spy = jest.spyOn(element, 'focus'); findSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ARROW_DOWN_KEY })); expect(spy).toHaveBeenCalled(); diff --git a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js index 7efc0e162b8..a67276ac64a 100644 --- a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js @@ -11,6 +11,7 @@ import searchProjectsWithinGroupQuery from '~/issues/list/queries/search_project import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility'; import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; +import { stubComponent } from 'helpers/stub_component'; import { emptySearchProjectsQueryResponse, emptySearchProjectsWithinGroupQueryResponse, @@ -42,6 +43,7 @@ describe('NewResourceDropdown component', () => { queryResponse = searchProjectsQueryResponse, mountFn = shallowMount, propsData = {}, + stubs = {}, } = {}) => { const requestHandlers = [[query, jest.fn().mockResolvedValue(queryResponse)]]; const apolloProvider = createMockApollo(requestHandlers); @@ -49,6 +51,9 @@ describe('NewResourceDropdown component', () => { wrapper = mountFn(NewResourceDropdown, { apolloProvider, propsData, + stubs: { + ...stubs, + }, }); }; @@ -81,13 +86,18 @@ describe('NewResourceDropdown component', () => { }); it('focuses on input when dropdown is shown', async () => { - mountComponent({ mountFn: mount }); - - const inputSpy = jest.spyOn(findInput().vm, 'focusInput'); + const inputMock = jest.fn(); + mountComponent({ + stubs: { + GlSearchBoxByType: stubComponent(GlSearchBoxByType, { + methods: { focusInput: inputMock }, + }), + }, + }); await showDropdown(); - expect(inputSpy).toHaveBeenCalledTimes(1); + expect(inputMock).toHaveBeenCalledTimes(1); }); describe.each` diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js index 58d5a1a63ba..86dc9afaacc 100644 --- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js @@ -117,22 +117,18 @@ describe('Source Viewer component', () => { }); it('calls the query only once per chunk', async () => { - jest.spyOn(wrapper.vm.$apollo, 'query'); - // We trigger the `appear` event multiple times here in order to simulate the user scrolling past the chunk more than once. // In this scenario we only want to query the backend once. await triggerChunkAppear(); await triggerChunkAppear(); - expect(wrapper.vm.$apollo.query).toHaveBeenCalledTimes(1); + expect(blameDataQueryHandlerSuccess).toHaveBeenCalledTimes(1); }); it('requests blame information for overlapping chunk', async () => { - jest.spyOn(wrapper.vm.$apollo, 'query'); - await triggerChunkAppear(1); - expect(wrapper.vm.$apollo.query).toHaveBeenCalledTimes(2); + expect(blameDataQueryHandlerSuccess).toHaveBeenCalledTimes(2); expect(blameDataQueryHandlerSuccess).toHaveBeenCalledWith( expect.objectContaining({ fromLine: 71, toLine: 110 }), ); diff --git a/spec/lib/api/entities/hook_spec.rb b/spec/lib/api/entities/hook_spec.rb new file mode 100644 index 00000000000..45648d6fb64 --- /dev/null +++ b/spec/lib/api/entities/hook_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Entities::Hook, feature_category: :webhooks do + let(:hook) { create(:project_hook) } + let(:with_url_variables) { true } + let(:entity) { described_class.new(hook, with_url_variables: with_url_variables) } + + subject(:json) { entity.as_json } + + it 'exposes correct attributes' do + expect(json.keys).to contain_exactly(:alert_status, :created_at, :disabled_until, :enable_ssl_verification, :id, + :merge_requests_events, :push_events, :repository_update_events, :tag_push_events, :url, :url_variables + ) + end + + context 'when `with_url_variables` is set to false' do + let(:with_url_variables) { false } + + it 'does not expose `with_url_variables` field' do + expect(json.keys).not_to include(:url_variables) + end + end +end diff --git a/spec/lib/gitlab/event_store/store_spec.rb b/spec/lib/gitlab/event_store/store_spec.rb index 04d0706c130..e747027db98 100644 --- a/spec/lib/gitlab/event_store/store_spec.rb +++ b/spec/lib/gitlab/event_store/store_spec.rb @@ -263,12 +263,59 @@ RSpec.describe Gitlab::EventStore::Store, feature_category: :shared do end end + describe '#publish_group' do + let(:event1) { event_klass.new(data: { name: 'Bob', id: 123 }) } + let(:event2) { event_klass.new(data: { name: 'Alice', id: 456 }) } + let(:event3) { event_klass.new(data: { name: 'Eva', id: 789 }) } + + let(:group_size) { 3 } + let(:events) { [event1, event2, event3] } + let(:serialized_data) { events.map(&:data).map(&:deep_stringify_keys) } + + let(:store) do + described_class.new do |s| + s.subscribe worker, to: event_klass, group_size: group_size + end + end + + subject { store.publish_group(events) } + + context 'with valid events' do + it 'calls consume_events of subscription' do + expect(store.subscriptions[event_klass].first).to receive(:consume_events).with(events) + + subject + end + end + + context 'when there is invalid event' do + let(:events) { [event1, invalid_event] } + + context 'when event is invalid' do + let(:invalid_event) { stub_const('TestEvent', {}) } + + it 'raises InvalidEvent error' do + expect { subject }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + + context 'when one of the events is a different event' do + let(:invalid_event) { stub_const('DifferentEvent', Class.new(Gitlab::EventStore::Event)) } + + it 'raises InvalidEvent error' do + expect { subject }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + end + end + describe 'subscriber' do let(:data) { { name: 'Bob', id: 123 } } + let(:event_data) { data } let(:event_name) { event.class.name } let(:worker_instance) { worker.new } - subject { worker_instance.perform(event_name, data) } + subject { worker_instance.perform(event_name, event_data) } it 'is a Sidekiq worker' do expect(worker_instance).to be_a(ApplicationWorker) @@ -278,7 +325,7 @@ RSpec.describe Gitlab::EventStore::Store, feature_category: :shared do expect(worker_instance).to receive(:handle_event).with(instance_of(event.class)) expect_any_instance_of(event.class) do |event| - expect(event).to receive(:data).and_return(data) + expect(event).to receive(:data).and_return(event_data) end subject @@ -299,5 +346,24 @@ RSpec.describe Gitlab::EventStore::Store, feature_category: :shared do expect { subject }.to raise_error(NotImplementedError) end end + + context 'when there are multiple events' do + let(:event_data) { [{ name: 'Bob', id: 123 }, { name: 'Alice', id: 456 }] } + + let(:first_event) { event_klass.new(data: event_data.first) } + let(:second_event) { event_klass.new(data: event_data.last) } + + before do + allow(worker_instance).to receive(:construct_event).with(event_klass, event_data.first).and_return(first_event) + allow(worker_instance).to receive(:construct_event).with(event_klass, event_data.last).and_return(second_event) + end + + it 'calls handle_event multiple times' do + expect(worker_instance).to receive(:handle_event).once.with(first_event) + expect(worker_instance).to receive(:handle_event).once.with(second_event) + + subject + end + end end end diff --git a/spec/lib/gitlab/event_store/subscription_spec.rb b/spec/lib/gitlab/event_store/subscription_spec.rb new file mode 100644 index 00000000000..2a87f48be10 --- /dev/null +++ b/spec/lib/gitlab/event_store/subscription_spec.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::EventStore::Subscription, feature_category: :shared do + let(:worker) do + stub_const('EventSubscriber', Class.new).tap do |klass| + klass.class_eval do + include Gitlab::EventStore::Subscriber + + def handle_event(event) + event.data + end + end + end + end + + let(:event_klass) { stub_const('TestEvent', Class.new(Gitlab::EventStore::Event)) } + let(:event) { event_klass.new(data: data) } + + let(:delay) { nil } + let(:condition) { nil } + let(:group_size) { nil } + + subject(:subscription) { described_class.new(worker, condition, delay, group_size) } + + before do + event_klass.class_eval do + def schema + { + 'required' => %w[name id], + 'type' => 'object', + 'properties' => { + 'name' => { 'type' => 'string' }, + 'id' => { 'type' => 'integer' } + } + } + end + end + end + + describe '#consume_events' do + let(:event1) { event_klass.new(data: { name: 'Bob', id: 123 }) } + let(:event2) { event_klass.new(data: { name: 'Alice', id: 456 }) } + let(:event3) { event_klass.new(data: { name: 'Eva', id: 789 }) } + + let(:group_size) { 3 } + let(:events) { [event1, event2, event3] } + let(:serialized_data) { events.map(&:data).map(&:deep_stringify_keys) } + + subject(:consume_events) { subscription.consume_events(events) } + + context 'with invalid events' do + let(:events) { [event1, invalid_event] } + + context 'when event is invalid' do + let(:invalid_event) { stub_const('TestEvent', Class.new { attr_reader :data }).new } + + it 'raises InvalidEvent error' do + expect { consume_events }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + + context 'when one of the events is a different event' do + let(:invalid_event_klass) { stub_const('DifferentEvent', Class.new(Gitlab::EventStore::Event)) } + let(:invalid_event) { invalid_event_klass.new(data: {}) } + + before do + invalid_event_klass.class_eval do + def schema + { + 'type' => 'object', + 'properties' => {} + } + end + end + end + + it 'raises InvalidEvent error' do + expect { consume_events }.to raise_error(Gitlab::EventStore::InvalidEvent) + end + end + end + + context 'when grouped events size is more than batch scheduling size' do + let(:group_size) { 2 } + + before do + stub_const("#{described_class}::SCHEDULING_BATCH_SIZE", 1) + end + + it 'dispatches the events to the worker with batch parameters' do + expect(worker).to receive(:bulk_perform_in).with( + 1.second, + [['TestEvent', serialized_data.take(2)], ['TestEvent', serialized_data.drop(2)]], + batch_size: 1, + batch_delay: 10.seconds + ) + + consume_events + end + + context 'with delayed dispatching of event' do + let(:delay) { 1.minute } + + it 'dispatches the events to the worker with batch parameters and delay' do + expect(worker).to receive(:bulk_perform_in).with( + 1.minute, + [['TestEvent', serialized_data.take(2)], ['TestEvent', serialized_data.drop(2)]], + batch_size: 1, + batch_delay: 10.seconds + ) + + consume_events + end + end + end + + context 'when subscription has grouped dispatching of events' do + let(:group_size) { 2 } + + it 'dispatches the events to the worker in group' do + expect(worker).to receive(:bulk_perform_async).once.with([ + ['TestEvent', serialized_data.take(2)], + ['TestEvent', serialized_data.drop(2)] + ]) + + consume_events + end + end + + context 'when subscription has delayed dispatching of event' do + let(:delay) { 1.minute } + + it 'dispatches the events to the worker after some time' do + expect(worker).to receive(:bulk_perform_in).with(1.minute, [['TestEvent', serialized_data]]) + + consume_events + end + end + end +end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 48fb74b13f8..569dac7ff5b 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -2513,7 +2513,7 @@ - './ee/spec/services/ee/merge_requests/after_create_service_spec.rb' - './ee/spec/services/ee/merge_requests/base_service_spec.rb' - './ee/spec/services/ee/merge_requests/create_approval_event_service_spec.rb' -- './ee/spec/services/ee/merge_requests/create_from_vulnerability_data_service_spec.rb' +- './ee/spec/services/merge_requests/create_from_vulnerability_data_service_spec.rb' - './ee/spec/services/ee/merge_requests/create_pipeline_service_spec.rb' - './ee/spec/services/ee/merge_requests/create_service_spec.rb' - './ee/spec/services/ee/merge_requests/execute_approval_hooks_service_spec.rb' diff --git a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb index 7489dc7c1d6..de458bc87db 100644 --- a/spec/support/shared_examples/requests/api/hooks_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/hooks_shared_examples.rb @@ -84,7 +84,7 @@ RSpec.shared_examples 'web-hook API endpoints' do |prefix| end end - context 'the hook has URL variables' do + context 'the hook has URL variables', if: prefix != '/projects/:id' do before do hook.update!(url_variables: { 'token' => 'supers3cret' }) end |