diff options
Diffstat (limited to 'spec/models/concerns')
-rw-r--r-- | spec/models/concerns/ci/partitionable_spec.rb | 84 | ||||
-rw-r--r-- | spec/models/concerns/disables_sti_spec.rb | 15 | ||||
-rw-r--r-- | spec/models/concerns/enums/sbom_spec.rb | 62 | ||||
-rw-r--r-- | spec/models/concerns/ignorable_columns_spec.rb | 18 | ||||
-rw-r--r-- | spec/models/concerns/pg_full_text_searchable_spec.rb | 14 | ||||
-rw-r--r-- | spec/models/concerns/routable_spec.rb | 139 | ||||
-rw-r--r-- | spec/models/concerns/transitionable_spec.rb | 17 | ||||
-rw-r--r-- | spec/models/concerns/triggerable_hooks_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/concerns/vulnerability_finding_helpers_spec.rb | 27 |
9 files changed, 298 insertions, 80 deletions
diff --git a/spec/models/concerns/ci/partitionable_spec.rb b/spec/models/concerns/ci/partitionable_spec.rb index 6daafc78cff..735b81f54bc 100644 --- a/spec/models/concerns/ci/partitionable_spec.rb +++ b/spec/models/concerns/ci/partitionable_spec.rb @@ -5,6 +5,12 @@ require 'spec_helper' RSpec.describe Ci::Partitionable do let(:ci_model) { Class.new(Ci::ApplicationRecord) } + around do |ex| + Gitlab::Database::SharedModel.using_connection(ci_model.connection) do + ex.run + end + end + describe 'partitionable models inclusion' do subject { ci_model.include(described_class) } @@ -61,10 +67,58 @@ RSpec.describe Ci::Partitionable do context 'when partitioned is true' do let(:partitioned) { true } + let(:partitioning_strategy) { ci_model.partitioning_strategy } it { expect(ci_model.ancestors).to include(PartitionedTable) } - it { expect(ci_model.partitioning_strategy).to be_a(Gitlab::Database::Partitioning::CiSlidingListStrategy) } - it { expect(ci_model.partitioning_strategy.partitioning_key).to eq(:partition_id) } + it { expect(partitioning_strategy).to be_a(Gitlab::Database::Partitioning::CiSlidingListStrategy) } + it { expect(partitioning_strategy.partitioning_key).to eq(:partition_id) } + + describe 'next_partition_if callback' do + let(:active_partition) { partitioning_strategy.active_partition } + + let(:table_options) do + { + primary_key: [:id, :partition_id], + options: 'PARTITION BY LIST (partition_id)', + if_not_exists: false + } + end + + before do + ci_model.connection.create_table(:_test_table_name, **table_options) do |t| + t.bigserial :id, null: false + t.bigint :partition_id, null: false + end + + ci_model.table_name = :_test_table_name + end + + subject(:value) { partitioning_strategy.next_partition_if.call(active_partition) } + + context 'without any existing partitions' do + it { is_expected.to eq(true) } + end + + context 'with initial partition attached' do + before do + ci_model.connection.execute(<<~SQL) + CREATE TABLE IF NOT EXISTS _test_table_name_100 PARTITION OF _test_table_name FOR VALUES IN (100); + SQL + end + + it { is_expected.to eq(true) } + end + + context 'with an existing partition for partition_id = 101' do + before do + ci_model.connection.execute(<<~SQL) + CREATE TABLE IF NOT EXISTS _test_table_name_101 PARTITION OF _test_table_name FOR VALUES IN (101); + SQL + end + + it { is_expected.to eq(false) } + end + end end context 'when partitioned is false' do @@ -74,4 +128,30 @@ RSpec.describe Ci::Partitionable do it { expect(ci_model).not_to respond_to(:partitioning_strategy) } end end + + describe '.in_partition' do + before do + stub_const("#{described_class}::Testing::PARTITIONABLE_MODELS", [ci_model.name]) + ci_model.table_name = :p_ci_builds + ci_model.include(described_class) + end + + subject(:scope_values) { ci_model.in_partition(value).where_values_hash } + + context 'with integer parameters' do + let(:value) { 101 } + + it 'adds a partition_id filter' do + expect(scope_values).to include('partition_id' => 101) + end + end + + context 'with partitionable records' do + let(:value) { build_stubbed(:ci_pipeline, partition_id: 101) } + + it 'adds a partition_id filter' do + expect(scope_values).to include('partition_id' => 101) + end + end + end end diff --git a/spec/models/concerns/disables_sti_spec.rb b/spec/models/concerns/disables_sti_spec.rb new file mode 100644 index 00000000000..07eea635289 --- /dev/null +++ b/spec/models/concerns/disables_sti_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe DisablesSti, feature_category: :shared do + describe '.allow_legacy_sti_class' do + it 'is nil by default' do + expect(ApplicationRecord.allow_legacy_sti_class).to eq(nil) + 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/concerns/enums/sbom_spec.rb b/spec/models/concerns/enums/sbom_spec.rb index e2f56cc637d..3bbdf619a8c 100644 --- a/spec/models/concerns/enums/sbom_spec.rb +++ b/spec/models/concerns/enums/sbom_spec.rb @@ -3,9 +3,9 @@ require "spec_helper" RSpec.describe Enums::Sbom, feature_category: :dependency_management do - describe '.purl_types' do - using RSpec::Parameterized::TableSyntax + using RSpec::Parameterized::TableSyntax + describe '.purl_types' do subject(:actual_purl_type) { described_class.purl_types[package_manager] } where(:given_package_manager, :expected_purl_type) do @@ -35,5 +35,63 @@ RSpec.describe Enums::Sbom, feature_category: :dependency_management do expect(actual_purl_type).to eql(expected_purl_type) end end + + it 'contains all of the dependency scanning and container scanning purl types' do + expect(described_class::DEPENDENCY_SCANNING_PURL_TYPES + described_class::CONTAINER_SCANNING_PURL_TYPES) + .to eql(described_class::PURL_TYPES.keys) + end + end + + describe '.dependency_scanning_purl_type?' do + where(:purl_type, :expected) do + :composer | false + 'composer' | true + 'conan' | true + 'gem' | true + 'golang' | true + 'maven' | true + 'npm' | true + 'nuget' | true + 'pypi' | true + 'unknown' | false + 'apk' | false + 'rpm' | false + 'deb' | false + 'wolfi' | false + end + + with_them do + it 'returns true if the purl_type is for dependency_scanning' do + actual = described_class.dependency_scanning_purl_type?(purl_type) + expect(actual).to eql(expected) + end + end + end + + describe '.container_scanning_purl_type?' do + where(:purl_type, :expected) do + 'composer' | false + 'conan' | false + 'gem' | false + 'golang' | false + 'maven' | false + 'npm' | false + 'nuget' | false + 'pypi' | false + 'unknown' | false + :apk | false + 'apk' | true + 'rpm' | true + 'deb' | true + 'cbl-mariner' | true + 'wolfi' | true + end + + with_them do + it 'returns true if the purl_type is for container_scanning' do + actual = described_class.container_scanning_purl_type?(purl_type) + expect(actual).to eql(expected) + end + end end end diff --git a/spec/models/concerns/ignorable_columns_spec.rb b/spec/models/concerns/ignorable_columns_spec.rb index 339f06f9c45..44dc0bb6da6 100644 --- a/spec/models/concerns/ignorable_columns_spec.rb +++ b/spec/models/concerns/ignorable_columns_spec.rb @@ -27,6 +27,12 @@ RSpec.describe IgnorableColumns do expect { subject.ignore_columns(:name, remove_after: nil, remove_with: 12.6) }.to raise_error(ArgumentError, /Please indicate/) end + it 'allows setting remove_never: true and not setting other remove options' do + expect do + subject.ignore_columns(%i[name created_at], remove_never: true) + end.to change { subject.ignored_columns }.from([]).to(%w[name created_at]) + end + it 'requires remove_after attribute to be set' do expect { subject.ignore_columns(:name, remove_after: "not a date", remove_with: 12.6) }.to raise_error(ArgumentError, /Please indicate/) end @@ -73,9 +79,11 @@ RSpec.describe IgnorableColumns do end describe IgnorableColumns::ColumnIgnore do - subject { described_class.new(remove_after, remove_with) } + subject { described_class.new(remove_after, remove_with, remove_never) } + let(:remove_after) { nil } let(:remove_with) { double } + let(:remove_never) { false } describe '#safe_to_remove?' do context 'after remove_after date has passed' do @@ -93,6 +101,14 @@ RSpec.describe IgnorableColumns do expect(subject.safe_to_remove?).to be_falsey end end + + context 'with remove_never: true' do + let(:remove_never) { true } + + it 'is false' do + expect(subject.safe_to_remove?).to be_falsey + end + end end end end diff --git a/spec/models/concerns/pg_full_text_searchable_spec.rb b/spec/models/concerns/pg_full_text_searchable_spec.rb index 7da48489e12..f3289408643 100644 --- a/spec/models/concerns/pg_full_text_searchable_spec.rb +++ b/spec/models/concerns/pg_full_text_searchable_spec.rb @@ -87,6 +87,12 @@ RSpec.describe PgFullTextSearchable, feature_category: :global_search do [english, with_accent, japanese].each(&:update_search_data!) end + it 'builds a search query using `search_vector` from the search_data table' do + sql = model_class.pg_full_text_search('test').to_sql + + expect(sql).to include('"issue_search_data"."search_vector" @@ to_tsquery') + end + it 'searches across all fields' do expect(model_class.pg_full_text_search('title english')).to contain_exactly(english, japanese) end @@ -158,6 +164,14 @@ RSpec.describe PgFullTextSearchable, feature_category: :global_search do end end + describe '.pg_full_text_search_in_model' do + it 'builds a search query using `search_vector` from the model table' do + sql = model_class.pg_full_text_search_in_model('test').to_sql + + expect(sql).to include('"issues"."search_vector" @@ to_tsquery') + end + end + describe '#update_search_data!' do let(:model) { model_class.create!(project: project, namespace: project.project_namespace, title: 'title', description: 'description') } diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 7e324812b97..e71392f7bbc 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.shared_examples 'routable resource' do - shared_examples_for '.find_by_full_path' do |has_cross_join: false| + shared_examples_for '.find_by_full_path' do it 'finds records by their full path' do expect(described_class.find_by_full_path(record.full_path)).to eq(record) expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record) @@ -46,22 +46,98 @@ RSpec.shared_examples 'routable resource' do end end - if has_cross_join - it 'has a cross-join' do - expect(Gitlab::Database).to receive(:allow_cross_joins_across_databases) + it 'does not have cross-join' do + expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases) - described_class.find_by_full_path(record.full_path) + described_class.find_by_full_path(record.full_path) + end + end + + it_behaves_like '.find_by_full_path', :aggregate_failures + + shared_examples_for '.where_full_path_in' do + context 'without any paths' do + it 'returns an empty relation' do + expect(described_class.where_full_path_in([])).to eq([]) + end + end + + context 'without any valid paths' do + it 'returns an empty relation' do + expect(described_class.where_full_path_in(%w[unknown])).to eq([]) + end + end + + context 'with valid paths' do + it 'returns the entities matching the paths' do + result = described_class.where_full_path_in([record.full_path, record_2.full_path]) + + expect(result).to contain_exactly(record, record_2) + end + + it 'returns entities regardless of the casing of paths' do + result = described_class.where_full_path_in([record.full_path.upcase, record_2.full_path.upcase]) + + expect(result).to contain_exactly(record, record_2) + end + end + + context 'on the usage of `use_includes` parameter' do + let_it_be(:klass) { record.class.to_s.downcase } + let_it_be(:record_3) { create(:"#{klass}") } + let_it_be(:record_4) { create(:"#{klass}") } + + context 'when use_includes: true' do + it 'includes route information when loading records' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: true) + .map(&:route) + end + + expect do + described_class.where_full_path_in( + [ + record.full_path, + record_2.full_path, + record_3.full_path, + record_4.full_path + ], use_includes: true) + .map(&:route) + end.to issue_same_number_of_queries_as(control_count) + end end - else - it 'does not have cross-join' do - expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases) - described_class.find_by_full_path(record.full_path) + context 'when use_includes: false' do + it 'does not include route information when loading records' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: false) + .map(&:route) + end + + expect do + described_class.where_full_path_in( + [ + record.full_path, + record_2.full_path, + record_3.full_path, + record_4.full_path + ], use_includes: false) + .map(&:route) + end.not_to issue_same_number_of_queries_as(control_count) + end end end end - it_behaves_like '.find_by_full_path', :aggregate_failures + it_behaves_like '.where_full_path_in', :aggregate_failures + + context 'when the `optimize_where_full_path_in` feature flag is turned OFF' do + before do + stub_feature_flags(optimize_where_full_path_in: false) + end + + it_behaves_like '.where_full_path_in', :aggregate_failures + end end RSpec.shared_examples 'routable resource with parent' do @@ -105,10 +181,12 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr it_behaves_like 'routable resource' do let_it_be(:record) { group } + let_it_be(:record_2) { nested_group } end it_behaves_like 'routable resource with parent' do let_it_be(:record) { nested_group } + let_it_be(:record_2) { group } end describe 'Validations' do @@ -169,34 +247,6 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr expect(group.route.namespace).to eq(group) end - describe '.where_full_path_in' do - context 'without any paths' do - it 'returns an empty relation' do - expect(described_class.where_full_path_in([])).to eq([]) - end - end - - context 'without any valid paths' do - it 'returns an empty relation' do - expect(described_class.where_full_path_in(%w[unknown])).to eq([]) - end - end - - context 'with valid paths' do - it 'returns the projects matching the paths' do - result = described_class.where_full_path_in([group.to_param, nested_group.to_param]) - - expect(result).to contain_exactly(group, nested_group) - end - - it 'returns projects regardless of the casing of paths' do - result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase]) - - expect(result).to contain_exactly(group, nested_group) - end - end - end - describe '#parent_loaded?' do before do group.parent = create(:group) @@ -232,9 +282,11 @@ end RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) { create(:project, namespace: namespace) } + let_it_be(:project_2) { create(:project) } it_behaves_like 'routable resource with parent' do let_it_be(:record) { project } + let_it_be(:record_2) { project_2 } end it 'creates route with namespace referencing project namespace' do @@ -252,6 +304,17 @@ RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: : expect(record).to be_nil end end + + describe '.where_full_path_in' do + it 'does not return records if the sources are different, but the IDs match' do + group = create(:group, id: 1992) + project = create(:project, id: 1992) + + records = described_class.where(id: project.id).where_full_path_in([group.full_path]) + + expect(records).to be_empty + end + end end RSpec.describe Namespaces::ProjectNamespace, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do diff --git a/spec/models/concerns/transitionable_spec.rb b/spec/models/concerns/transitionable_spec.rb index b80d363ef78..c8cba1ae226 100644 --- a/spec/models/concerns/transitionable_spec.rb +++ b/spec/models/concerns/transitionable_spec.rb @@ -22,19 +22,16 @@ RSpec.describe Transitionable, feature_category: :code_review_workflow do let(:object) { klass.new(transitioning) } describe '#transitioning?' do - where(:transitioning, :feature_flag, :result) do - true | true | true - false | false | false - true | false | false - false | true | false + context "when trasnitioning" do + let(:transitioning) { true } + + it { expect(object.transitioning?).to eq(true) } end - with_them do - before do - stub_feature_flags(skip_validations_during_transitions: feature_flag) - end + context "when not trasnitioning" do + let(:transitioning) { false } - it { expect(object.transitioning?).to eq(result) } + it { expect(object.transitioning?).to eq(false) } end end end diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb index 28cda269458..c209d6476f3 100644 --- a/spec/models/concerns/triggerable_hooks_spec.rb +++ b/spec/models/concerns/triggerable_hooks_spec.rb @@ -10,6 +10,8 @@ RSpec.describe TriggerableHooks do include TriggerableHooks # rubocop:disable RSpec/DescribedClass triggerable_hooks [:push_hooks] + self.allow_legacy_sti_class = true + scope :executable, -> { all } end end diff --git a/spec/models/concerns/vulnerability_finding_helpers_spec.rb b/spec/models/concerns/vulnerability_finding_helpers_spec.rb deleted file mode 100644 index 023ecccb520..00000000000 --- a/spec/models/concerns/vulnerability_finding_helpers_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe VulnerabilityFindingHelpers do - let(:cls) do - Class.new do - include VulnerabilityFindingHelpers - - attr_accessor :report_type - - def initialize(report_type) - @report_type = report_type - end - end - end - - describe '#requires_manual_resolution?' do - it 'returns false if the finding does not require manual resolution' do - expect(cls.new('sast').requires_manual_resolution?).to eq(false) - end - - it 'returns true when the finding requires manual resolution' do - expect(cls.new('secret_detection').requires_manual_resolution?).to eq(true) - end - end -end |