diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-15 03:12:58 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-15 03:12:58 +0300 |
commit | 45465a1f217b65ee3b11870175f363afaf912eb9 (patch) | |
tree | 0f4103ad51619ed03fc47cc28e32df3fb57b5c0c /spec | |
parent | 046498496e140f96beb63ff45ec9b0bb9acdbdd3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 481 insertions, 374 deletions
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 80c68d3e091..b2b591d7929 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -657,5 +657,23 @@ RSpec.describe Projects::MergeRequests::DiffsController, feature_category: :code end end end + + context 'when collapse_generated_diff_files FF is enabled' do + it 'sets generated' do + go + expect(json_response["diff_files"][0]["viewer"]["generated"]).to eq(false) + end + end + + context 'when collapse_generated_diff_files FF is disabled' do + before do + stub_feature_flags(collapse_generated_diff_files: false) + end + + it 'sets generated as nil' do + go + expect(json_response["diff_files"][0]["viewer"]["generated"]).to be_nil + end + end end end diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js index 90c42e6e5db..a9fbf4632ac 100644 --- a/spec/frontend/diffs/components/diff_file_spec.js +++ b/spec/frontend/diffs/components/diff_file_spec.js @@ -399,6 +399,27 @@ describe('DiffFile', () => { }); }); + describe('automatically collapsed generated file', () => { + beforeEach(() => { + makeFileAutomaticallyCollapsed(store); + const file = store.state.diffs.diffFiles[0]; + Object.assign(store.state.diffs.diffFiles[0], { + ...file, + viewer: { + ...file.viewer, + generated: true, + }, + }); + }); + + it('should show the generated file warning with expansion button', () => { + expect(findDiffContentArea(wrapper).html()).toContain( + 'Generated files are collapsed by default. This behavior can be overriden via .gitattributes file if required.', + ); + expect(findToggleButton(wrapper).exists()).toBe(true); + }); + }); + describe('not collapsed', () => { beforeEach(() => { makeFileOpenByDefault(store); diff --git a/spec/frontend/emoji/components/emoji_group_spec.js b/spec/frontend/emoji/components/emoji_group_spec.js index 75397ce25ff..a2a46bedd7b 100644 --- a/spec/frontend/emoji/components/emoji_group_spec.js +++ b/spec/frontend/emoji/components/emoji_group_spec.js @@ -1,5 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; +import { GlButton } from '@gitlab/ui'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import EmojiGroup from '~/emoji/components/emoji_group.vue'; @@ -10,6 +11,9 @@ function factory(propsData = {}) { wrapper = extendedWrapper( shallowMount(EmojiGroup, { propsData, + stubs: { + GlButton, + }, }), ); } @@ -19,7 +23,6 @@ describe('Emoji group component', () => { factory({ emojis: [], renderGroup: false, - clickEmoji: jest.fn(), }); expect(wrapper.findByTestId('emoji-button').exists()).toBe(false); @@ -29,24 +32,20 @@ describe('Emoji group component', () => { factory({ emojis: ['thumbsup', 'thumbsdown'], renderGroup: true, - clickEmoji: jest.fn(), }); expect(wrapper.findAllByTestId('emoji-button').exists()).toBe(true); expect(wrapper.findAllByTestId('emoji-button').length).toBe(2); }); - it('calls clickEmoji', () => { - const clickEmoji = jest.fn(); - + it('emits emoji-click', () => { factory({ emojis: ['thumbsup', 'thumbsdown'], renderGroup: true, - clickEmoji, }); - wrapper.findByTestId('emoji-button').trigger('click'); + wrapper.findComponent(GlButton).vm.$emit('click'); - expect(clickEmoji).toHaveBeenCalledWith('thumbsup'); + expect(wrapper.emitted('emoji-click')).toStrictEqual([['thumbsup']]); }); }); 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 520a56db713..2d9356ca96d 100644 --- a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb +++ b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb @@ -175,7 +175,6 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat metric_definition = base_metric_definition.merge( "key_path" => key_path, "time_frame" => time_frame, - "instrumentation_class" => "RedisHLLMetric", "events" => [{ "name" => event, "unique" => unique }] ).merge(metric_definition_extra) expect(YAML.safe_load(File.read(metric_definition_path))).to eq(metric_definition) @@ -193,7 +192,6 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat metric_definition = base_metric_definition.merge( "key_path" => key_path, "time_frame" => time_frame, - "instrumentation_class" => "TotalCountMetric", "events" => [{ "name" => event }] ).merge(metric_definition_extra) expect(YAML.safe_load(File.read(metric_definition_path))).to eq(metric_definition) diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb index eb98c1cc7e6..f8d67a6f0b4 100644 --- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb @@ -66,6 +66,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr let_it_be(:assignees) { create_list(:user, 2) } let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:labels) { create_list(:label, 2) } + let(:merge_request_description) { nil } let(:merge_request) do create(:merge_request, :simple, @@ -73,6 +74,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr target_project: project, assignees: assignees, milestone: milestone, + description: merge_request_description, labels: labels) end @@ -114,6 +116,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr ).to_s, 'CI_MERGE_REQUEST_TITLE' => merge_request.title, 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description, + 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'false', 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','), @@ -122,6 +125,78 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr 'CI_MERGE_REQUEST_SQUASH_ON_MERGE' => merge_request.squash_on_merge?.to_s end + context 'when merge request description hits the limit' do + let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH + 1) } + + it 'truncates the exposed description' do + truncated_description = merge_request.description.truncate( + MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH + ) + expect(subject.to_hash) + .to include( + 'CI_MERGE_REQUEST_DESCRIPTION' => truncated_description, + 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'true' + ) + end + end + + context 'when merge request description fits the length limit' do + let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH - 1) } + + it 'does not truncate the exposed description' do + expect(subject.to_hash) + .to include( + 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description, + 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'false' + ) + end + end + + context 'when truncate_ci_merge_request_description feature flag is disabled' do + before do + stub_feature_flags(truncate_ci_merge_request_description: false) + end + + context 'when merge request description hits the limit' do + let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH + 1) } + + it 'does not truncate the exposed description' do + expect(subject.to_hash) + .to include( + 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description + ) + expect(subject.to_hash) + .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED') + end + end + + context 'when merge request description fits the length limit' do + let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH - 1) } + + it 'does not truncate the exposed description' do + expect(subject.to_hash) + .to include( + 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description + ) + expect(subject.to_hash) + .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED') + end + end + + context 'when merge request description does not exist' do + let(:merge_request_description) { nil } + + it 'does not truncate the exposed description' do + expect(subject.to_hash) + .to include( + 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description + ) + expect(subject.to_hash) + .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED') + end + end + end + it 'exposes diff variables' do expect(subject.to_hash) .to include( diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index 4ab4d047522..dc60d486f49 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -598,6 +598,32 @@ RSpec.describe Gitlab::Git::DiffCollection, feature_category: :source_code_manag end end + context 'with existing generated value in the hash' do + let(:collection) do + described_class.new([{ diff: 'some content', generated: true }], options) + end + + context 'when collapse_generated on' do + let(:options) { { collapse_generated: true } } + + it 'sets the diff as generated' do + collection.each do |diff| + expect(diff.generated).to eq true + end + end + end + + context 'when collapse_generated off' do + let(:options) { { collapse_generated: false } } + + it 'does not set the diff as generated' do + collection.each do |diff| + expect(diff.generated).to be_nil + end + end + end + end + context 'when diff are too large' do let(:collection) do described_class.new([{ diff: 'a' * 204800 }]) diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 2fca81299e0..c40445433c0 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -156,6 +156,31 @@ EOT expect(diff).to be_collapsed end end + + context 'when the file is set as generated' do + let(:diff) { described_class.new(gitaly_diff, generated: true, expanded: expanded) } + let(:raw_patch) { 'some text' } + + context 'when expanded is set to false' do + let(:expanded) { false } + + it 'will be marked as generated and collapsed' do + expect(diff).to be_generated + expect(diff).to be_collapsed + expect(diff.diff).to be_empty + end + end + + context 'when expanded is set to true' do + let(:expanded) { true } + + it 'will still be marked as generated, but not as collapsed' do + expect(diff).to be_generated + expect(diff).not_to be_collapsed + expect(diff.diff).not_to be_empty + end + end + end end context 'using a Gitaly::CommitDelta' do diff --git a/spec/lib/gitlab/github_import/representation/representable_spec.rb b/spec/lib/gitlab/github_import/representation/representable_spec.rb new file mode 100644 index 00000000000..4bc495c40f5 --- /dev/null +++ b/spec/lib/gitlab/github_import/representation/representable_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Representation::Representable, feature_category: :importers do + let(:representation_class) do + subject_module = described_class + + Class.new do + include subject_module + end + end + + let(:representable) { representation_class.new } + + describe '#github_identifiers' do + subject(:github_identifiers) { representable.github_identifiers } + + context 'when class does not define `#github_identifiers`' do + it 'tracks the error' do + error = NotImplementedError.new('Subclasses must implement #github_identifiers') + + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(error) + is_expected.to eq({}) + end + end + + context 'when class defines `#github_identifiers`' do + let(:representation_class) do + Class.new(super()) do + def github_identifiers + { id: 1 } + end + end + end + + it 'does not track an exception and returns the identifiers' do + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + is_expected.to eq({ id: 1 }) + end + end + end +end diff --git a/spec/lib/gitlab/hook_data/project_builder_spec.rb b/spec/lib/gitlab/hook_data/project_builder_spec.rb index f80faac563d..9d5eaf0608c 100644 --- a/spec/lib/gitlab/hook_data/project_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/project_builder_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe Gitlab::HookData::ProjectBuilder do let_it_be(:user) { create(:user, name: 'John', email: 'john@example.com') } - let_it_be(:namespace) { create(:namespace, owner: user) } - let_it_be(:project) { create(:project, :internal, name: 'my_project', namespace: namespace) } + let_it_be(:user2) { create(:user, name: 'Peter') } + let_it_be(:user3_non_owner) { create(:user, name: 'Not_Owner') } describe '#build' do let(:data) { described_class.new(project).build(event) } @@ -24,13 +24,13 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do expect(data[:created_at]).to eq(project.created_at.xmlschema) expect(data[:updated_at]).to eq(project.updated_at.xmlschema) - expect(data[:name]).to eq('my_project') + expect(data[:name]).to eq(project.name) expect(data[:path]).to eq(project.path) expect(data[:path_with_namespace]).to eq(project.full_path) expect(data[:project_id]).to eq(project.id) - expect(data[:owner_name]).to eq('John') - expect(data[:owner_email]).to eq(_('[REDACTED]')) - expect(data[:owners]).to contain_exactly({ name: 'John', email: _('[REDACTED]') }) + expect(data[:owner_name]).to eq(owner_name) + expect(data[:owner_email]).to eq(owner_email) + expect(data[:owners]).to match_array(owners_data) expect(data[:project_visibility]).to eq('internal') end end @@ -48,40 +48,104 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do end end - context 'on create' do - let(:event) { :create } + context 'the project is created in a personal namespace' do + let(:owner_name) { user.name } + let(:owner_email) { _('[REDACTED]') } + let(:owners_data) { [{ name: 'John', email: _('[REDACTED]') }, { name: 'Peter', email: _('[REDACTED]') }] } + let_it_be(:namespace) { create(:namespace, owner: user) } + let_it_be(:project) { create(:project, :internal, name: 'personal project', namespace: namespace) } - it { expect(event_name).to eq('project_create') } + before_all do + project.add_owner(user2) + project.add_maintainer(user3_non_owner) + end - it_behaves_like 'includes the required attributes' - it_behaves_like 'does not include `old_path_with_namespace` attribute' - end + context 'on create' do + let(:event) { :create } - context 'on destroy' do - let(:event) { :destroy } + it { expect(event_name).to eq('project_create') } - it { expect(event_name).to eq('project_destroy') } + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include `old_path_with_namespace` attribute' + end - it_behaves_like 'includes the required attributes' - it_behaves_like 'does not include `old_path_with_namespace` attribute' - end + context 'on destroy' do + let(:event) { :destroy } + + it { expect(event_name).to eq('project_destroy') } + + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include `old_path_with_namespace` attribute' + end - context 'on rename' do - let(:event) { :rename } + context 'on rename' do + let(:event) { :rename } - it { expect(event_name).to eq('project_rename') } + it { expect(event_name).to eq('project_rename') } - it_behaves_like 'includes the required attributes' - it_behaves_like 'includes `old_path_with_namespace` attribute' + it_behaves_like 'includes the required attributes' + it_behaves_like 'includes `old_path_with_namespace` attribute' + end + + context 'on transfer' do + let(:event) { :transfer } + + it { expect(event_name).to eq('project_transfer') } + + it_behaves_like 'includes the required attributes' + it_behaves_like 'includes `old_path_with_namespace` attribute' + end end - context 'on transfer' do - let(:event) { :transfer } + context 'the project is created in a group' do + let(:owner_name) { group.name } + let(:owner_email) { "" } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :internal, name: 'group project', namespace: group) } + let(:owners_data) { [{ name: 'John', email: _('[REDACTED]') }, { email: "[REDACTED]", name: "Peter" }] } + + before_all do + group.add_owner(user) + group.add_owner(user2) + group.add_maintainer(user3_non_owner) + end + + # Repeat the tests in the previous context + context 'on create' do + let(:event) { :create } - it { expect(event_name).to eq('project_transfer') } + it { expect(event_name).to eq('project_create') } - it_behaves_like 'includes the required attributes' - it_behaves_like 'includes `old_path_with_namespace` attribute' + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include `old_path_with_namespace` attribute' + end + + context 'on destroy' do + let(:event) { :destroy } + + it { expect(event_name).to eq('project_destroy') } + + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include `old_path_with_namespace` attribute' + end + + context 'on rename' do + let(:event) { :rename } + + it { expect(event_name).to eq('project_rename') } + + it_behaves_like 'includes the required attributes' + it_behaves_like 'includes `old_path_with_namespace` attribute' + end + + context 'on transfer' do + let(:event) { :transfer } + + it { expect(event_name).to eq('project_transfer') } + + it_behaves_like 'includes the required attributes' + it_behaves_like 'includes `old_path_with_namespace` attribute' + end end end end diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 08adc031631..fb46d48c1bb 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -46,6 +46,34 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping end end + describe '.instrumentation_class' do + context 'for non internal events' do + let(:attributes) { { key_path: 'metric1', instrumentation_class: 'RedisHLLMetric', data_source: 'redis_hll' } } + + it 'returns class from the definition' do + expect(definition.instrumentation_class).to eq('RedisHLLMetric') + end + end + + context 'for internal events' do + context 'for total counter' do + let(:attributes) { { key_path: 'metric1', data_source: 'internal_events', events: [{ name: 'a' }] } } + + it 'returns TotalCounterMetric' do + expect(definition.instrumentation_class).to eq('TotalCountMetric') + end + end + + context 'for uniq counter' do + let(:attributes) { { key_path: 'metric1', data_source: 'internal_events', events: [{ name: 'a', unique: :id }] } } + + it 'returns RedisHLLMetric' do + expect(definition.instrumentation_class).to eq('RedisHLLMetric') + end + end + end + end + describe 'not_removed' do let(:all_definitions) do metrics_definitions = [ @@ -71,12 +99,13 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping describe '#with_instrumentation_class' do let(:all_definitions) do metrics_definitions = [ - { key_path: 'metric1', instrumentation_class: 'RedisHLLMetric', status: 'active' }, - { key_path: 'metric2', instrumentation_class: 'RedisHLLMetric', status: 'broken' }, - { key_path: 'metric3', instrumentation_class: 'RedisHLLMetric', status: 'active' }, - { key_path: 'metric4', instrumentation_class: 'RedisHLLMetric', status: 'removed' }, - { key_path: 'metric5', status: 'active' }, - { key_path: 'metric_missing_status' } + { key_path: 'metric1', status: 'active', data_source: 'redis_hll', instrumentation_class: 'RedisHLLMetric' }, + { key_path: 'metric2', status: 'active', data_source: 'internal_events' }, # class is defined by data_source + + { key_path: 'metric3', status: 'active', data_source: 'redis_hll' }, + { key_path: 'metric4', status: 'removed', instrumentation_class: 'RedisHLLMetric', data_source: 'redis_hll' }, + { key_path: 'metric5', status: 'removed', data_source: 'internal_events' }, + { key_path: 'metric_missing_status', data_source: 'internal_events' } ] metrics_definitions.map { |definition| described_class.new(definition[:key_path], definition.symbolize_keys) } end @@ -86,15 +115,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping end it 'includes definitions with instrumentation_class' do - expect(described_class.with_instrumentation_class.count).to eq(3) - end - - context 'with removed metric' do - let(:metric_status) { 'removed' } - - it 'excludes removed definitions' do - expect(described_class.with_instrumentation_class.count).to eq(3) - end + expect(described_class.with_instrumentation_class.map(&:key_path)).to match_array(%w[metric1 metric2]) end end @@ -224,25 +245,9 @@ RSpec.describe Gitlab::Usage::MetricDefinition, feature_category: :service_ping where(:instrumentation_class, :options, :events, :is_valid) do 'AnotherClass' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false - nil | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false - 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | true + 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false 'RedisHLLMetric' | { events: ['a'] } | nil | false - 'RedisHLLMetric' | nil | [{ name: 'a', unique: 'user.id' }] | false - 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'a' }] | false - 'RedisHLLMetric' | { events: 'a' } | [{ name: 'a', unique: 'user.id' }] | false - 'RedisHLLMetric' | { events: [2] } | [{ name: 'a', unique: 'user.id' }] | false - 'RedisHLLMetric' | { events: ['a'], a: 'b' } | [{ name: 'a', unique: 'user.id' }] | false - 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id', b: 'c' }] | false - 'RedisHLLMetric' | { events: ['a'] } | [{ name: 'a' }] | false - 'RedisHLLMetric' | { events: ['a'] } | [{ unique: 'user.id' }] | false - 'TotalCountMetric' | { events: ['a'] } | [{ name: 'a' }] | true - 'TotalCountMetric' | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | false - 'TotalCountMetric' | { events: ['a'] } | nil | false - 'TotalCountMetric' | nil | [{ name: 'a' }] | false - 'TotalCountMetric' | { events: [2] } | [{ name: 'a' }] | false - 'TotalCountMetric' | { events: ['a'] } | [{}] | false - 'TotalCountMetric' | 'a' | [{ name: 'a' }] | false - 'TotalCountMetric' | { events: ['a'], a: 'b' } | [{ name: 'a' }] | false + nil | { events: ['a'] } | [{ name: 'a', unique: 'user.id' }] | true end with_them do diff --git a/spec/models/ci/catalog/resource_spec.rb b/spec/models/ci/catalog/resource_spec.rb index 8ed7369e3d7..b597586e899 100644 --- a/spec/models/ci/catalog/resource_spec.rb +++ b/spec/models/ci/catalog/resource_spec.rb @@ -117,6 +117,82 @@ RSpec.describe Ci::Catalog::Resource, feature_category: :pipeline_composition do end end + describe 'authorized catalog resources' do + let_it_be(:namespace) { create(:group) } + let_it_be(:other_namespace) { create(:group) } + let_it_be(:other_user) { create(:user) } + + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:internal_project) { create(:project, :internal) } + let_it_be(:internal_namespace_project) { create(:project, :internal, namespace: namespace) } + let_it_be(:private_namespace_project) { create(:project, namespace: namespace) } + let_it_be(:other_private_namespace_project) { create(:project, namespace: other_namespace) } + + let_it_be(:public_resource) { create(:ci_catalog_resource, project: public_project) } + let_it_be(:internal_resource) { create(:ci_catalog_resource, project: internal_project) } + let_it_be(:internal_namespace_resource) { create(:ci_catalog_resource, project: internal_namespace_project) } + let_it_be(:private_namespace_resource) { create(:ci_catalog_resource, project: private_namespace_project) } + + let_it_be(:other_private_namespace_resource) do + create(:ci_catalog_resource, project: other_private_namespace_project) + end + + before_all do + namespace.add_reporter(current_user) + other_namespace.add_guest(other_user) + end + + describe '.public_or_visible_to_user' do + subject(:resources) { described_class.public_or_visible_to_user(current_user) } + + it 'returns all resources visible to the user' do + expect(resources).to contain_exactly( + public_resource, internal_resource, internal_namespace_resource, private_namespace_resource) + end + + context 'with a different user' do + let(:current_user) { other_user } + + it 'returns all resources visible to the user' do + expect(resources).to contain_exactly( + public_resource, internal_resource, internal_namespace_resource, other_private_namespace_resource) + end + end + + context 'when the user is nil' do + let(:current_user) { nil } + + it 'returns only public resources' do + expect(resources).to contain_exactly(public_resource) + end + end + end + + describe '.visible_to_user' do + subject(:resources) { described_class.visible_to_user(current_user) } + + it "returns resources belonging to the user's authorized namespaces" do + expect(resources).to contain_exactly(internal_namespace_resource, private_namespace_resource) + end + + context 'with a different user' do + let(:current_user) { other_user } + + it "returns resources belonging to the user's authorized namespaces" do + expect(resources).to contain_exactly(other_private_namespace_resource) + end + end + + context 'when the user is nil' do + let(:current_user) { nil } + + it 'does not return any resources' do + expect(resources).to be_empty + end + end + end + end + describe '#state' do it 'defaults to draft' do expect(resource_a.state).to eq('draft') diff --git a/spec/models/concerns/disables_sti_spec.rb b/spec/models/concerns/disables_sti_spec.rb index 85b943c3563..07eea635289 100644 --- a/spec/models/concerns/disables_sti_spec.rb +++ b/spec/models/concerns/disables_sti_spec.rb @@ -3,53 +3,13 @@ require 'spec_helper' RSpec.describe DisablesSti, feature_category: :shared do - describe '#new' do - context 'for a non STI class like Project' do - it 'can initialize' do - expect { Project.new }.not_to raise_error - end + describe '.allow_legacy_sti_class' do + it 'is nil by default' do + expect(ApplicationRecord.allow_legacy_sti_class).to eq(nil) end - context 'for a base class which has an inheritance column' do - it 'can initialize' do - expect { Label.new }.not_to raise_error - end - end - - context 'for an STI class that previously existed' do - it 'can initialize' do - expect { GroupLabel.new }.not_to raise_error - end - end - - context 'for an STI class that is new' do - before do - stub_const('DummyModel', Class.new(Label)) - end - - it 'cannot initialize' do - expect { DummyModel.new }.to raise_error(/Do not use Single Table Inheritance/) - end - - context 'when SKIP_STI_CHECK is true' do - before do - stub_const("#{described_class}::SKIP_STI_CHECK", 'true') - end - - it 'can initialize' do - expect { DummyModel.new }.not_to raise_error - end - end - end - - context 'for an STI class descending from Integration' do - before do - stub_const('IntegrationDummyModel', Class.new(Integration)) - end - - it 'can initialize' do - expect { IntegrationDummyModel.new }.not_to raise_error - end + it 'is true on legacy models' do + expect(PersonalSnippet.allow_legacy_sti_class).to eq(true) end end end diff --git a/spec/models/every_model_spec.rb b/spec/models/every_model_spec.rb index b1ed4e6c145..479fc8d3dfa 100644 --- a/spec/models/every_model_spec.rb +++ b/spec/models/every_model_spec.rb @@ -2,19 +2,10 @@ require 'spec_helper' -RSpec.describe "Every model", feature_category: :shared do +RSpec.describe 'Every model', feature_category: :shared do describe 'disallows STI', :eager_load do - let(:models) { ApplicationRecord.descendants.reject(&:abstract_class?) } - - it 'does not allow STI', :aggregate_failures do - models.each do |model| - next if model == model.base_class - next if model.allow_legacy_sti_class - - expect(model).not_to have_attribute(model.inheritance_column), - "Do not use Single Table Inheritance (`#{model.name}` inherits `#{model.base_class.name}`). " \ - "See https://docs.gitlab.com/ee/development/database/single_table_inheritance.html" - end + include_examples 'Model disables STI' do + let(:models) { ApplicationRecord.descendants.reject(&:abstract_class?) } end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index bff2ec641f3..891d251c7e2 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -11,10 +11,11 @@ RSpec.describe Group, feature_category: :groups_and_projects do describe 'associations' do it { is_expected.to have_many :projects } it { is_expected.to have_many(:all_group_members).dependent(:destroy) } + it { is_expected.to have_many(:all_owner_members) } it { is_expected.to have_many(:group_members).dependent(:destroy) } it { is_expected.to have_many(:namespace_members) } it { is_expected.to have_many(:users).through(:group_members) } - it { is_expected.to have_many(:owners).through(:all_group_members) } + it { is_expected.to have_many(:owners).through(:all_owner_members) } it { is_expected.to have_many(:requesters).dependent(:destroy) } it { is_expected.to have_many(:namespace_requesters) } it { is_expected.to have_many(:members_and_requesters) } diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb index 64ad25f559d..00376e1a871 100644 --- a/spec/models/project_authorization_spec.rb +++ b/spec/models/project_authorization_spec.rb @@ -110,6 +110,23 @@ RSpec.describe ProjectAuthorization, feature_category: :groups_and_projects do end end + describe '.owners' do + let_it_be(:project_original_owner_authorization) { project.owner.project_authorizations.first } + let_it_be(:project_authorization_owner) { create(:project_authorization, :owner, project: project) } + + before_all do + create(:project_authorization, :guest, project: project) + create(:project_authorization, :developer, project: project) + end + + it 'returns all records which only have Owners access' do + expect(described_class.owners.map(&:attributes)).to match_array([ + project_original_owner_authorization, + project_authorization_owner + ].map(&:attributes)) + end + end + describe '.for_project' do let_it_be(:project_2) { create(:project, namespace: user.namespace) } let_it_be(:project_3) { create(:project, namespace: user.namespace) } diff --git a/spec/requests/api/graphql/ci/catalog/resources_spec.rb b/spec/requests/api/graphql/ci/catalog/resources_spec.rb index 23d91f6dd76..49a3f3be1d7 100644 --- a/spec/requests/api/graphql/ci/catalog/resources_spec.rb +++ b/spec/requests/api/graphql/ci/catalog/resources_spec.rb @@ -60,7 +60,7 @@ RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_compositi it do ctx = { current_user: user } - control_count = ActiveRecord::QueryRecorder.new do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do run_with_clean_state(query, context: ctx) end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 55aaadce97c..5e19043639d 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -9328,8 +9328,6 @@ - './spec/views/layouts/devise.html.haml_spec.rb' - './spec/views/layouts/_flash.html.haml_spec.rb' - './spec/views/layouts/fullscreen.html.haml_spec.rb' -- './spec/views/layouts/header/_gitlab_version.html.haml_spec.rb' -- './spec/views/layouts/header/_new_dropdown.haml_spec.rb' - './spec/views/layouts/_head.html.haml_spec.rb' - './spec/views/layouts/profile.html.haml_spec.rb' - './spec/views/layouts/_published_experiments.html.haml_spec.rb' diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb index 24f0d22da47..b51c68f4958 100644 --- a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb +++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb @@ -50,7 +50,8 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do 'value_type' => value_type, 'status' => status, 'instrumentation_class' => instrumentation_class, - 'time_frame' => 'all' + 'time_frame' => 'all', + 'data_source' => 'redis_hll' } end end diff --git a/spec/support/shared_examples/models/disable_sti_shared_examples.rb b/spec/support/shared_examples/models/disable_sti_shared_examples.rb new file mode 100644 index 00000000000..090592827d1 --- /dev/null +++ b/spec/support/shared_examples/models/disable_sti_shared_examples.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Checks whether STI is disabled in +models+. +# +# Parameter: +# - models: List of model classes +RSpec.shared_examples 'Model disables STI' do + skip_sti_check = Gitlab::Utils.to_boolean(ENV['SKIP_STI_CHECK'], default: false) + + it 'does not allow STI', :aggregate_failures, unless: skip_sti_check do + models.each do |model| + next unless model + next unless model < ApplicationRecord + next if model == model.base_class + next if model.allow_legacy_sti_class + + expect(model).not_to have_attribute(model.inheritance_column), + "Do not use Single Table Inheritance (`#{model.name}` inherits `#{model.base_class.name}`). " \ + "See https://docs.gitlab.com/ee/development/database/single_table_inheritance.html" + end + end +end + +RSpec.shared_examples 'STI disabled', type: :model do # rubocop:disable RSpec/SharedGroupsMetadata -- Shared example is run within every spec tagged `type: :model` + include_examples 'Model disables STI' do + let(:models) { [described_class] } + end +end diff --git a/spec/tooling/lib/tooling/predictive_tests_spec.rb b/spec/tooling/lib/tooling/predictive_tests_spec.rb index fdb7d09a3e2..e49daa70295 100644 --- a/spec/tooling/lib/tooling/predictive_tests_spec.rb +++ b/spec/tooling/lib/tooling/predictive_tests_spec.rb @@ -106,6 +106,7 @@ RSpec.describe Tooling::PredictiveTests, feature_category: :tooling do context 'when some files used for frontend fixtures were changed' do let(:changed_files_content) { 'app/models/todo.rb' } let(:changed_files_matching_test) { 'spec/models/todo_spec.rb' } + let(:additional_matching_tests) { 'spec/models/every_model_spec.rb' } let(:matching_frontend_fixture) { 'tmp/tests/frontend/fixtures-ee/todos/todos.html' } let(:fixtures_mapping_content) do JSON.dump(changed_files_matching_test => [matching_frontend_fixture]) # rubocop:disable Gitlab/Json @@ -120,7 +121,7 @@ RSpec.describe Tooling::PredictiveTests, feature_category: :tooling do it 'appends the spec file to RSPEC_MATCHING_TESTS_PATH' do expect { subject }.to change { File.read(matching_tests.path) } .from(matching_tests_initial_content) - .to("#{matching_tests_initial_content} #{changed_files_matching_test}") + .to("#{matching_tests_initial_content} #{additional_matching_tests} #{changed_files_matching_test}") end it 'does not change files other than RSPEC_CHANGED_FILES_PATH nor RSPEC_MATCHING_TESTS_PATH' do diff --git a/spec/views/layouts/header/_gitlab_version.html.haml_spec.rb b/spec/views/layouts/header/_gitlab_version.html.haml_spec.rb deleted file mode 100644 index a027bdd6357..00000000000 --- a/spec/views/layouts/header/_gitlab_version.html.haml_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'layouts/header/_gitlab_version' do - describe 'when show_version_check? is true' do - before do - allow(view).to receive(:show_version_check?).and_return(true) - render - end - - it 'renders the version check badge' do - expect(rendered).to have_selector('.js-gitlab-version-check-badge') - end - - it 'renders the container as a link' do - expect(rendered).to have_selector( - 'a[data-testid="gitlab-version-container"][href="/help/update/index"]' - ) - end - - it 'renders the container with correct data-tracking attributes' do - expect(rendered).to have_selector( - 'a[data-testid="gitlab-version-container"][data-track-action="click_link"]' - ) - - expect(rendered).to have_selector( - 'a[data-testid="gitlab-version-container"][data-track-label="version_help_dropdown"]' - ) - - expect(rendered).to have_selector( - 'a[data-testid="gitlab-version-container"][data-track-property="navigation_top"]' - ) - end - end -end diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb deleted file mode 100644 index ef028da7ab9..00000000000 --- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb +++ /dev/null @@ -1,204 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'layouts/header/_new_dropdown', feature_category: :navigation do - let_it_be(:user) { create(:user) } # rubocop:disable RSpec/FactoryBot/AvoidCreate - - shared_examples_for 'invite member selector' do - context 'with ability to invite members' do - it { is_expected.to have_selector('.js-invite-members-trigger') } - end - - context 'without ability to invite members' do - let(:invite_member) { false } - - it { is_expected.not_to have_selector('.js-invite-members-trigger') } - end - end - - context 'with group-specific links' do - let_it_be(:group) { create(:group) } # rubocop:disable RSpec/FactoryBot/AvoidCreate - - before do - stub_current_user(user) - - assign(:group, group) - end - - context 'as a Group owner' do - before do - group.add_owner(user) - end - - it 'has a "New project" link' do - render - - expect(rendered).to have_link('New project', href: new_project_path(namespace_id: group.id)) - end - - it 'has a "New subgroup" link' do - render - - expect(rendered) - .to have_link('New subgroup', href: new_group_path(parent_id: group.id, anchor: 'create-group-pane')) - end - end - - describe 'invite members item' do - let(:href) { group_group_members_path(group) } - let(:invite_member) { true } - - before do - allow(view).to receive(:can?).with(user, :create_projects, group).and_return(true) - allow(view).to receive(:can?).with(user, :admin_group_member, group).and_return(invite_member) - allow(view).to receive(:can_admin_group_member?).and_return(invite_member) - end - - subject do - render - - rendered - end - - it_behaves_like 'invite member selector' - end - end - - context 'with project-specific links' do - let_it_be(:project) { create(:project, creator: user, namespace: user.namespace) } # rubocop:disable RSpec/FactoryBot/AvoidCreate - - before do - assign(:project, project) - end - - context 'as a Project owner' do - before do - stub_current_user(user) - end - - it 'has a "New issue" link' do - render - - expect(rendered).to have_link('New issue', href: new_project_issue_path(project)) - end - - it 'has a "New merge request" link' do - render - - expect(rendered).to have_link('New merge request', href: project_new_merge_request_path(project)) - end - - it 'has a "New snippet" link' do - render - - expect(rendered).to have_link('New snippet', href: new_project_snippet_path(project)) - end - end - - context 'as a Project guest' do - let_it_be(:guest) { create(:user) } # rubocop:disable RSpec/FactoryBot/AvoidCreate - - before do - stub_current_user(guest) - project.add_guest(guest) - end - - it 'has no "New merge request" link' do - render - - expect(rendered).not_to have_link('New merge request') - end - - it 'has no "New snippet" link' do - render - - expect(rendered).not_to have_link('New snippet', href: new_project_snippet_path(project)) - end - end - - describe 'invite members item' do - let(:invite_member) { true } - let(:href) { project_project_members_path(project) } - - before do - allow(view).to receive(:can_admin_project_member?).and_return(invite_member) - stub_current_user(user) - end - - subject do - render - - rendered - end - - it_behaves_like 'invite member selector' - end - end - - context 'with global links' do - before do - stub_current_user(user) - end - - it 'has a "New project" link' do - render - - expect(rendered).to have_link('New project', href: new_project_path) - end - - it 'has a "New group" link' do - render - - expect(rendered).to have_link('New group', href: new_group_path) - end - - it 'has a "New snippet" link' do - render - - expect(rendered).to have_link('New snippet', href: new_snippet_path) - end - - context 'when partial exists in a menu item' do - it 'renders the menu item partial without rendering invite modal partial' do - view_model = { - title: '_title_', - menu_sections: [ - { - title: '_section_title_', - menu_items: [ - ::Gitlab::Nav::TopNavMenuItem - .build(id: '_id_', title: '_title_', partial: 'groups/invite_members_top_nav_link') - ] - } - ] - } - - allow(view).to receive(:new_dropdown_view_model).and_return(view_model) - - render - - expect(response).to render_template(partial: 'groups/_invite_members_top_nav_link') - end - end - - context 'when the user is not allowed to do anything' do - let(:user) { create(:user, :external) } # rubocop:disable RSpec/FactoryBot/AvoidCreate - - before do - allow(user).to receive(:can?).and_call_original - allow(user).to receive(:can?).with(:create_organization).and_return(false) - end - - it 'is nil' do - # We have to use `view.render` because `render` causes issues - # https://github.com/rails/rails/issues/41320 - expect(view.render("layouts/header/new_dropdown")).to be_nil - end - end - end - - def stub_current_user(current_user) - allow(view).to receive(:current_user).and_return(current_user) - end -end |