Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-28 21:11:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-28 21:11:01 +0300
commit7c5f1bfac791045e54386b9c9bb56ee24afc68ca (patch)
treea11c8dff3994899c25acacb383c0a70522a24cd1 /spec
parentd62fd6e04c272d48dccde4033529ca97c27502f6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb38
-rw-r--r--spec/factories/ci/reports/sbom/components.rb2
-rw-r--r--spec/frontend/tracing/components/tracing_details_spec.js103
-rw-r--r--spec/frontend/tracing/components/tracing_list_spec.js12
-rw-r--r--spec/frontend/tracing/components/tracing_table_list_spec.js16
-rw-r--r--spec/frontend/tracing/details_index_spec.js42
-rw-r--r--spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/component_spec.rb26
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb93
-rw-r--r--spec/requests/api/graphql/mutations/work_items/subscribe_spec.rb73
-rw-r--r--spec/services/ci/pipeline_schedules/create_service_spec.rb8
-rw-r--r--spec/services/ci/pipeline_schedules/update_service_spec.rb10
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb34
-rw-r--r--spec/support/shared_examples/ci/pipeline_schedules_create_or_update_shared_examples.rb121
-rw-r--r--spec/support/shared_examples/controllers/internal_event_tracking_examples.rb9
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