diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 21:11:01 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 21:11:01 +0300 |
commit | 7c5f1bfac791045e54386b9c9bb56ee24afc68ca (patch) | |
tree | a11c8dff3994899c25acacb383c0a70522a24cd1 /spec | |
parent | d62fd6e04c272d48dccde4033529ca97c27502f6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
15 files changed, 570 insertions, 19 deletions
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 6d810fdcd51..e4d87daa8d7 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -6,8 +6,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu include AccessMatchersForController let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } + let_it_be_with_reload(:project) { create(:project, :public, :repository) } + let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } before do project.add_developer(user) @@ -137,6 +137,20 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu expect(v.variable_type).to eq("file") end end + + context 'when the user is not allowed to create a pipeline schedule with variables' do + before do + project.update!(restrict_user_defined_variables: true) + end + + it 'does not create a new schedule' do + expect { go } + .to not_change { Ci::PipelineSchedule.count } + .and not_change { Ci::PipelineScheduleVariable.count } + + expect(response).to have_gitlab_http_status(:ok) + end + end end context 'when variables_attributes has two variables and duplicated' do @@ -149,8 +163,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu it 'returns an error that the keys of variable are duplicated' do expect { go } - .to change { Ci::PipelineSchedule.count }.by(0) - .and change { Ci::PipelineScheduleVariable.count }.by(0) + .to not_change { Ci::PipelineSchedule.count } + .and not_change { Ci::PipelineScheduleVariable.count } expect(assigns(:schedule).errors['variables']).not_to be_empty end @@ -213,6 +227,22 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu expect(pipeline_schedule.variables.last.key).to eq('AAA') expect(pipeline_schedule.variables.last.value).to eq('AAA123') end + + context 'when the user is not allowed to update pipeline schedule variables' do + before do + project.update!(restrict_user_defined_variables: true) + end + + it 'does not update the schedule' do + expect { go } + .to not_change { Ci::PipelineScheduleVariable.count } + + expect(response).to have_gitlab_http_status(:ok) + + pipeline_schedule.reload + expect(pipeline_schedule.variables).to be_empty + end + end end context 'when params include two duplicated variables' do diff --git a/spec/factories/ci/reports/sbom/components.rb b/spec/factories/ci/reports/sbom/components.rb index 8f2c00b695a..76bfbe13acb 100644 --- a/spec/factories/ci/reports/sbom/components.rb +++ b/spec/factories/ci/reports/sbom/components.rb @@ -9,12 +9,14 @@ FactoryBot.define do transient do purl_type { 'npm' } + namespace { nil } end purl do ::Sbom::PackageUrl.new( type: purl_type, name: name, + namespace: namespace, version: version ).to_s end diff --git a/spec/frontend/tracing/components/tracing_details_spec.js b/spec/frontend/tracing/components/tracing_details_spec.js new file mode 100644 index 00000000000..c5efa2a7eb5 --- /dev/null +++ b/spec/frontend/tracing/components/tracing_details_spec.js @@ -0,0 +1,103 @@ +import { GlLoadingIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import TracingDetails from '~/tracing/components/tracing_details.vue'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createAlert } from '~/alert'; +import { visitUrl, isSafeURL } from '~/lib/utils/url_utility'; + +jest.mock('~/alert'); +jest.mock('~/lib/utils/url_utility'); + +describe('TracingDetails', () => { + let wrapper; + let observabilityClientMock; + + const TRACE_ID = 'test-trace-id'; + const TRACING_INDEX_URL = 'https://www.gitlab.com/flightjs/Flight/-/tracing'; + + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findTraceDetails = () => wrapper.findComponentByTestId('trace-details'); + + const props = { + traceId: TRACE_ID, + tracingIndexUrl: TRACING_INDEX_URL, + }; + + const mountComponent = async () => { + wrapper = shallowMountExtended(TracingDetails, { + propsData: { + ...props, + observabilityClient: observabilityClientMock, + }, + }); + await waitForPromises(); + }; + + beforeEach(() => { + isSafeURL.mockReturnValue(true); + + observabilityClientMock = { + isTracingEnabled: jest.fn(), + fetchTrace: jest.fn(), + }; + }); + + it('renders the loading indicator while checking if tracing is enabled', () => { + mountComponent(); + + expect(findLoadingIcon().exists()).toBe(true); + expect(observabilityClientMock.isTracingEnabled).toHaveBeenCalled(); + }); + + describe('when tracing is enabled', () => { + const mockTrace = { traceId: 'test-trace-id', foo: 'bar' }; + beforeEach(async () => { + observabilityClientMock.isTracingEnabled.mockResolvedValueOnce(true); + observabilityClientMock.fetchTrace.mockResolvedValueOnce(mockTrace); + + await mountComponent(); + }); + + it('fetches the trace and renders the trace details', () => { + expect(observabilityClientMock.isTracingEnabled).toHaveBeenCalled(); + expect(observabilityClientMock.fetchTrace).toHaveBeenCalled(); + expect(findLoadingIcon().exists()).toBe(false); + expect(findTraceDetails().exists()).toBe(true); + }); + }); + + describe('when tracing is not enabled', () => { + beforeEach(async () => { + observabilityClientMock.isTracingEnabled.mockResolvedValueOnce(false); + + await mountComponent(); + }); + + it('redirects to tracingIndexUrl', () => { + expect(visitUrl).toHaveBeenCalledWith(props.tracingIndexUrl); + }); + }); + + describe('error handling', () => { + it('if isTracingEnabled fails, it renders an alert and empty page', async () => { + observabilityClientMock.isTracingEnabled.mockRejectedValueOnce('error'); + + await mountComponent(); + + expect(createAlert).toHaveBeenCalledWith({ message: 'Failed to load trace details.' }); + expect(findLoadingIcon().exists()).toBe(false); + expect(findTraceDetails().exists()).toBe(false); + }); + + it('if fetchTrace fails, it renders an alert and empty page', async () => { + observabilityClientMock.isTracingEnabled.mockReturnValueOnce(true); + observabilityClientMock.fetchTrace.mockRejectedValueOnce('error'); + + await mountComponent(); + + expect(createAlert).toHaveBeenCalledWith({ message: 'Failed to load trace details.' }); + expect(findLoadingIcon().exists()).toBe(false); + expect(findTraceDetails().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/tracing/components/tracing_list_spec.js b/spec/frontend/tracing/components/tracing_list_spec.js index 183578cff31..f02b238f7a7 100644 --- a/spec/frontend/tracing/components/tracing_list_spec.js +++ b/spec/frontend/tracing/components/tracing_list_spec.js @@ -5,6 +5,8 @@ import TracingEmptyState from '~/tracing/components/tracing_empty_state.vue'; import TracingTableList from '~/tracing/components/tracing_table_list.vue'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert } from '~/alert'; +import * as urlUtility from '~/lib/utils/url_utility'; +import setWindowLocation from 'helpers/set_window_location_helper'; jest.mock('~/alert'); @@ -69,6 +71,16 @@ describe('TracingList', () => { expect(observabilityClientMock.fetchTraces).toHaveBeenCalledTimes(1); }); + + it('on trace selection it redirects to the details url', () => { + setWindowLocation('base_path'); + const visitUrlMock = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({}); + + findTableList().vm.$emit('trace-selected', { trace_id: 'test-trace-id' }); + + expect(visitUrlMock).toHaveBeenCalledTimes(1); + expect(visitUrlMock).toHaveBeenCalledWith('/base_path/test-trace-id'); + }); }); describe('when tracing is not enabled', () => { diff --git a/spec/frontend/tracing/components/tracing_table_list_spec.js b/spec/frontend/tracing/components/tracing_table_list_spec.js index 773b3eb8ed2..aa96b9b370f 100644 --- a/spec/frontend/tracing/components/tracing_table_list_spec.js +++ b/spec/frontend/tracing/components/tracing_table_list_spec.js @@ -1,3 +1,4 @@ +import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import TracingTableList from '~/tracing/components/tracing_table_list.vue'; @@ -27,13 +28,18 @@ describe('TracingTableList', () => { }; const getRows = () => wrapper.findComponent({ name: 'GlTable' }).find('tbody').findAll('tr'); - + const getRow = (idx) => getRows().at(idx); const getCells = (trIdx) => getRows().at(trIdx).findAll('td'); const getCell = (trIdx, tdIdx) => { return getCells(trIdx).at(tdIdx); }; + const selectRow = async (idx) => { + getRow(idx).trigger('click'); + await nextTick(); + }; + it('renders traces as table', () => { mountComponent(); @@ -50,6 +56,14 @@ describe('TracingTableList', () => { }); }); + it('emits trace-selected on row selection', async () => { + mountComponent(); + + await selectRow(0); + expect(wrapper.emitted('trace-selected')).toHaveLength(1); + expect(wrapper.emitted('trace-selected')[0][0]).toBe(mockTraces[0]); + }); + it('renders the empty state when no traces are provided', () => { mountComponent({ traces: [] }); diff --git a/spec/frontend/tracing/details_index_spec.js b/spec/frontend/tracing/details_index_spec.js new file mode 100644 index 00000000000..e0d368b6cb7 --- /dev/null +++ b/spec/frontend/tracing/details_index_spec.js @@ -0,0 +1,42 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import DetailsIndex from '~/tracing/details_index.vue'; +import TracingDetails from '~/tracing/components/tracing_details.vue'; +import ObservabilityContainer from '~/observability/components/observability_container.vue'; + +describe('DetailsIndex', () => { + const props = { + traceId: 'test-trace-id', + tracingIndexUrl: 'https://example.com/tracing/index', + oauthUrl: 'https://example.com/oauth', + tracingUrl: 'https://example.com/tracing', + provisioningUrl: 'https://example.com/provisioning', + }; + + let wrapper; + + const mountComponent = () => { + wrapper = shallowMountExtended(DetailsIndex, { + propsData: props, + }); + }; + + it('renders ObservabilityContainer component', () => { + mountComponent(); + + const observabilityContainer = wrapper.findComponent(ObservabilityContainer); + expect(observabilityContainer.exists()).toBe(true); + expect(observabilityContainer.props('oauthUrl')).toBe(props.oauthUrl); + expect(observabilityContainer.props('tracingUrl')).toBe(props.tracingUrl); + expect(observabilityContainer.props('provisioningUrl')).toBe(props.provisioningUrl); + }); + + it('renders TracingList component inside ObservabilityContainer', () => { + mountComponent(); + + const observabilityContainer = wrapper.findComponent(ObservabilityContainer); + const detailsCmp = observabilityContainer.findComponent(TracingDetails); + expect(detailsCmp.exists()).toBe(true); + expect(detailsCmp.props('traceId')).toBe(props.traceId); + expect(detailsCmp.props('tracingIndexUrl')).toBe(props.tracingIndexUrl); + }); +}); diff --git a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb index d9acd59aa71..ef09ad7aed4 100644 --- a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb +++ b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb @@ -83,7 +83,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat let(:identifiers) { %w[project user namespace] } let(:event_definition) do { - "category" => "GitlabInternalEvents", + "category" => "InternalEventTracking", "action" => event, "description" => description, "product_section" => section, diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb index b36924d42a5..d62d25aeefe 100644 --- a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb +++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb @@ -27,6 +27,28 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen ) end + describe '#name' do + subject { component.name } + + it { is_expected.to eq(name) } + + context 'with namespace' do + let(:purl) do + 'pkg:maven/org.NameSpace/Name@v0.0.1' + end + + it { is_expected.to eq('org.NameSpace/Name') } + + context 'when needing normalization' do + let(:purl) do + 'pkg:pypi/org.NameSpace/Name@v0.0.1' + end + + it { is_expected.to eq('org.namespace/name') } + end + end + end + describe '#<=>' do where do { @@ -47,7 +69,7 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen a_type: 'library', b_type: 'library', a_purl: 'pkg:npm/component-a@1.0.0', - b_purl: 'pkg:npm/component-a@1.0.0', + b_purl: 'pkg:npm/component-b@1.0.0', a_version: '1.0.0', b_version: '1.0.0', expected: -1 @@ -57,7 +79,7 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen b_name: 'component-a', a_type: 'library', b_type: 'library', - a_purl: 'pkg:npm/component-a@1.0.0', + a_purl: 'pkg:npm/component-b@1.0.0', b_purl: 'pkg:npm/component-a@1.0.0', a_version: '1.0.0', b_version: '1.0.0', diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 44887a86aff..dd633820ad9 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -4,6 +4,9 @@ require 'spec_helper' RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :shared do let(:policy) { ActionDispatch::ContentSecurityPolicy.new } + let(:lfs_enabled) { false } + let(:proxy_download) { false } + let(:csp_config) do { enabled: true, @@ -20,6 +23,32 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :s } end + let(:lfs_config) do + { + enabled: lfs_enabled, + remote_directory: 'lfs-objects', + connection: object_store_connection_config, + direct_upload: false, + proxy_download: proxy_download, + storage_options: {} + } + end + + let(:object_store_connection_config) do + { + provider: 'AWS', + aws_access_key_id: 'AWS_ACCESS_KEY_ID', + aws_secret_access_key: 'AWS_SECRET_ACCESS_KEY' + } + end + + before do + stub_lfs_setting(enabled: lfs_enabled) + allow(LfsObjectUploader) + .to receive(:object_store_options) + .and_return(GitlabSettings::Options.build(lfs_config)) + end + describe '.default_enabled' do let(:enabled) { described_class.default_enabled } @@ -170,6 +199,70 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader, feature_category: :s end end + describe 'LFS connect-src headers' do + let(:url_for_provider) { described_class.send(:build_lfs_url) } + + context 'when LFS is enabled' do + let(:lfs_enabled) { true } + + context 'and direct downloads are enabled' do + let(:provider) { LfsObjectUploader.object_store_options.connection.provider } + + context 'when provider is AWS' do + it { expect(provider).to eq('AWS') } + + it { expect(url_for_provider).to be_present } + + it { expect(directives['connect_src']).to include(url_for_provider) } + end + + context 'when provider is AzureRM' do + let(:object_store_connection_config) do + { + provider: 'AzureRM', + azure_storage_account_name: 'azuretest', + azure_storage_access_key: 'ABCD1234' + } + end + + it { expect(provider).to eq('AzureRM') } + + it { expect(url_for_provider).to be_present } + + it { expect(directives['connect_src']).to include(url_for_provider) } + end + + context 'when provider is Google' do + let(:object_store_connection_config) do + { + provider: 'Google', + google_project: 'GOOGLE_PROJECT', + google_application_default: true + } + end + + it { expect(provider).to eq('Google') } + + it { expect(url_for_provider).to be_present } + + it { expect(directives['connect_src']).to include(url_for_provider) } + end + end + + context 'but direct downloads are disabled' do + let(:proxy_download) { true } + + it { expect(directives['connect_src']).not_to include(url_for_provider) } + end + end + + context 'when LFS is disabled' do + let(:proxy_download) { true } + + it { expect(directives['connect_src']).not_to include(url_for_provider) } + end + end + describe 'CDN connections' do before do allow(described_class).to receive(:allow_letter_opener) diff --git a/spec/requests/api/graphql/mutations/work_items/subscribe_spec.rb b/spec/requests/api/graphql/mutations/work_items/subscribe_spec.rb new file mode 100644 index 00000000000..00672332082 --- /dev/null +++ b/spec/requests/api/graphql/mutations/work_items/subscribe_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Subscribe to a work item', feature_category: :team_planning do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private) } + let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } } + let_it_be(:work_item) { create(:work_item, project: project) } + + let(:subscribed_state) { true } + let(:mutation_input) { { 'id' => work_item.to_global_id.to_s, 'subscribed' => subscribed_state } } + let(:mutation) { graphql_mutation(:workItemSubscribe, mutation_input, fields) } + let(:mutation_response) { graphql_mutation_response(:work_item_subscribe) } + let(:fields) do + <<~FIELDS + workItem { + widgets { + type + ... on WorkItemWidgetNotifications { + subscribed + } + } + } + errors + FIELDS + end + + context 'when user is not allowed to update subscription work items' do + let(:current_user) { create(:user) } + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context + + context 'when user has permissions to update its subscription to the work items' do + let(:current_user) { guest } + + it "subscribe the user to the work item's notifications" do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { work_item.subscribed?(current_user, project) }.to(true) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['workItem']['widgets']).to include({ + 'type' => 'NOTIFICATIONS', + 'subscribed' => true + }) + end + + context 'when unsunscribing' do + let(:subscribed_state) { false } + + before do + create(:subscription, project: project, user: current_user, subscribable: work_item, subscribed: true) + end + + it "unsubscribe the user from the work item's notifications" do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { work_item.subscribed?(current_user, project) }.to(false) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['workItem']['widgets']).to include({ + 'type' => 'NOTIFICATIONS', + 'subscribed' => false + }) + end + end + end +end diff --git a/spec/services/ci/pipeline_schedules/create_service_spec.rb b/spec/services/ci/pipeline_schedules/create_service_spec.rb index a01c71432c3..3fc093c13da 100644 --- a/spec/services/ci/pipeline_schedules/create_service_spec.rb +++ b/spec/services/ci/pipeline_schedules/create_service_spec.rb @@ -3,9 +3,11 @@ require 'spec_helper' RSpec.describe Ci::PipelineSchedules::CreateService, feature_category: :continuous_integration do - let_it_be(:user) { create(:user) } let_it_be(:reporter) { create(:user) } - let_it_be(:project) { create(:project, :public, :repository) } + let_it_be_with_reload(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :public, :repository) } + + subject(:service) { described_class.new(project, user, params) } before_all do project.add_maintainer(user) @@ -82,5 +84,7 @@ RSpec.describe Ci::PipelineSchedules::CreateService, feature_category: :continuo end end end + + it_behaves_like 'pipeline schedules checking variables permission' end end diff --git a/spec/services/ci/pipeline_schedules/update_service_spec.rb b/spec/services/ci/pipeline_schedules/update_service_spec.rb index c31a652ed93..834bbcfcfeb 100644 --- a/spec/services/ci/pipeline_schedules/update_service_spec.rb +++ b/spec/services/ci/pipeline_schedules/update_service_spec.rb @@ -3,16 +3,18 @@ require 'spec_helper' RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuous_integration do - let_it_be(:user) { create(:user) } + let_it_be_with_reload(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :public, :repository) } + let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) } let_it_be(:reporter) { create(:user) } - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) } let_it_be(:pipeline_schedule_variable) do create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule) end + subject(:service) { described_class.new(pipeline_schedule, user, params) } + before_all do project.add_maintainer(user) project.add_reporter(reporter) @@ -123,5 +125,7 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo end end end + + it_behaves_like 'pipeline schedules checking variables permission' end end diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb index be470688084..b3185bc72ff 100644 --- a/spec/services/todos/destroy/group_private_service_spec.rb +++ b/spec/services/todos/destroy/group_private_service_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Todos::Destroy::GroupPrivateService, feature_category: :team_plan let!(:todo_group_member) { create(:todo, user: group_member, group: group) } let!(:todo_project_member) { create(:todo, user: project_member, group: group) } - describe '#execute' do + describe '#execute', :aggregate_failures do before do group.add_developer(group_member) project.add_developer(project_member) @@ -57,7 +57,37 @@ RSpec.describe Todos::Destroy::GroupPrivateService, feature_category: :team_plan end it 'removes todos only for users who are not group users' do - expect { subject }.to change { Todo.count }.from(7).to(5) + expect { subject }.to change { Todo.count }.from(7).to(4) + + expect(parent_member.todos).to contain_exactly(todo_parent_member) + expect(subgroup_member.todos).to be_empty + expect(subgproject_member.todos).to contain_exactly(todo_subproject_member) + end + end + + context 'with member via group share' do + let(:invited_group) { create(:group) } + let(:invited_group_member) { create(:user).tap { |u| invited_group.add_guest(u) } } + + let!(:todo_invited_group_member) { create(:todo, user: invited_group_member, group: group) } + + it 'does not remove todos for users invited to the group' do + create(:group_group_link, shared_group: group, shared_with_group: invited_group) + + expect { subject }.to change { Todo.count }.from(5).to(3) + + expect(invited_group_member.todos).to contain_exactly(todo_invited_group_member) + end + + it 'does not remove todos for users invited to an ancestor group' do + parent_group = create(:group) + group.update!(parent: parent_group) + + create(:group_group_link, shared_group: parent_group, shared_with_group: invited_group) + + expect { subject }.to change { Todo.count }.from(5).to(3) + + expect(invited_group_member.todos).to contain_exactly(todo_invited_group_member) end end end diff --git a/spec/support/shared_examples/ci/pipeline_schedules_create_or_update_shared_examples.rb b/spec/support/shared_examples/ci/pipeline_schedules_create_or_update_shared_examples.rb new file mode 100644 index 00000000000..399225c13b2 --- /dev/null +++ b/spec/support/shared_examples/ci/pipeline_schedules_create_or_update_shared_examples.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'pipeline schedules checking variables permission' do + let(:params) do + { + description: 'desc', + ref: 'patch-x', + active: false, + cron: '*/1 * * * *', + cron_timezone: 'UTC', + variables_attributes: variables_attributes + } + end + + shared_examples 'success response' do + it 'saves values with passed params' do + result = service.execute + + expect(result.status).to eq(:success) + expect(result.payload).to have_attributes( + description: 'desc', + ref: 'patch-x', + active: false, + cron: '*/1 * * * *', + cron_timezone: 'UTC' + ) + end + end + + shared_examples 'failure response' do + it 'does not save' do + result = service.execute + + expect(result.status).to eq(:error) + expect(result.reason).to eq(:forbidden) + expect(result.message).to match_array( + ['The current user is not authorized to set pipeline schedule variables'] + ) + end + end + + context 'when sending variables' do + let(:variables_attributes) do + [{ key: 'VAR2', secret_value: 'secret 2' }] + end + + shared_examples 'success response with variables' do + it_behaves_like 'success response' + + it 'saves variables' do + result = service.execute + + variables = result.payload.variables.map { |v| [v.key, v.value] } + + expect(variables).to include( + ['VAR2', 'secret 2'] + ) + end + end + + context 'when user is maintainer' do + it_behaves_like 'success response with variables' + end + + context 'when user is developer' do + before_all do + project.add_developer(user) + end + + it_behaves_like 'success response with variables' + end + + context 'when restrict_user_defined_variables is true' do + before_all do + project.update!(restrict_user_defined_variables: true) + end + + it_behaves_like 'success response with variables' + + context 'when user is developer' do + before_all do + project.add_developer(user) + end + + it_behaves_like 'failure response' + end + end + end + + context 'when not sending variables' do + let(:variables_attributes) { [] } + + context 'when user is maintainer' do + it_behaves_like 'success response' + end + + context 'when user is developer' do + before_all do + project.add_developer(user) + end + + it_behaves_like 'success response' + end + + context 'when restrict_user_defined_variables is true' do + before_all do + project.update!(restrict_user_defined_variables: true) + end + + it_behaves_like 'success response' + + context 'when user is developer' do + before_all do + project.add_developer(user) + end + + it_behaves_like 'success response' + end + end + end +end diff --git a/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb b/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb index e2a4fb31361..67f53fdf32d 100644 --- a/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb +++ b/spec/support/shared_examples/controllers/internal_event_tracking_examples.rb @@ -10,8 +10,6 @@ RSpec.shared_examples 'internal event tracking' do let(:fake_tracker) { instance_spy(Gitlab::Tracking::Destinations::Snowplow) } - let(:namespace) { nil } - let(:proejct) { nil } before do allow(Gitlab::Tracking).to receive(:tracker).and_return(fake_tracker) @@ -23,6 +21,9 @@ RSpec.shared_examples 'internal event tracking' do it 'logs to Snowplow', :aggregate_failures do subject + project = try(:project) + namespace = try(:namespace) + expect(Gitlab::Tracking::StandardContext) .to have_received(:new) .with( @@ -30,11 +31,12 @@ RSpec.shared_examples 'internal event tracking' do user_id: user.id, namespace_id: namespace&.id, plan_name: namespace&.actual_plan_name - ) + ).at_least(:once) expect(Gitlab::Tracking::ServicePingContext) .to have_received(:new) .with(data_source: :redis_hll, event: action) + .at_least(:once) expect(fake_tracker).to have_received(:event) .with( @@ -45,6 +47,5 @@ RSpec.shared_examples 'internal event tracking' do an_instance_of(SnowplowTracker::SelfDescribingJson) ] ) - .exactly(:once) end end |