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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-28 21:10:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-28 21:10:48 +0300
commit0076bbc67375ff1507e42ce479406daf92c0a6a2 (patch)
treeaa0a1c6f575ac050504c397c7edf8f9789d46046 /spec/models
parent8966e39395e22465ac3ff58407868b872a3ecffe (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r--spec/models/ci/build_metadata_spec.rb22
-rw-r--r--spec/models/concerns/ci/partitionable/switch_spec.rb294
-rw-r--r--spec/models/concerns/ci/partitionable_spec.rb21
-rw-r--r--spec/models/concerns/issuable_spec.rb16
-rw-r--r--spec/models/concerns/pg_full_text_searchable_spec.rb25
5 files changed, 374 insertions, 4 deletions
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 16cff72db64..b4c20637ce2 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -182,4 +182,26 @@ RSpec.describe Ci::BuildMetadata do
end
end
end
+
+ describe 'routing table switch' do
+ context 'with ff disabled' do
+ before do
+ stub_feature_flags(ci_partitioning_use_ci_builds_metadata_routing_table: false)
+ end
+
+ it 'uses the legacy table' do
+ expect(described_class.table_name).to eq('ci_builds_metadata')
+ end
+ end
+
+ context 'with ff enabled' do
+ before do
+ stub_feature_flags(ci_partitioning_use_ci_builds_metadata_routing_table: true)
+ end
+
+ it 'uses the routing table' do
+ expect(described_class.table_name).to eq('p_ci_builds_metadata')
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/ci/partitionable/switch_spec.rb b/spec/models/concerns/ci/partitionable/switch_spec.rb
new file mode 100644
index 00000000000..09005489268
--- /dev/null
+++ b/spec/models/concerns/ci/partitionable/switch_spec.rb
@@ -0,0 +1,294 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::Partitionable::Switch, :aggregate_failures do
+ let(:model) do
+ Class.new(Ci::ApplicationRecord) do
+ self.primary_key = :id
+ self.table_name = :_test_ci_jobs_metadata
+ self.sequence_name = :_test_ci_jobs_metadata_id_seq
+
+ def self.name
+ 'TestSwitchJobMetadata'
+ end
+ end
+ end
+
+ let(:table_rollout_flag) { :ci_partitioning_use_test_routing_table }
+
+ let(:partitioned_model) { model::Partitioned }
+
+ let(:jobs_model) do
+ Class.new(Ci::ApplicationRecord) do
+ self.primary_key = :id
+ self.table_name = :_test_ci_jobs
+
+ def self.name
+ 'TestSwitchJob'
+ end
+ end
+ end
+
+ before do
+ allow(ActiveSupport::DescendantsTracker).to receive(:store_inherited)
+
+ create_tables(<<~SQL)
+ CREATE TABLE _test_ci_jobs_metadata(
+ id serial NOT NULL PRIMARY KEY,
+ job_id int,
+ partition_id int NOT NULL DEFAULT 1,
+ expanded_environment_name text);
+
+ CREATE TABLE _test_p_ci_jobs_metadata (
+ LIKE _test_ci_jobs_metadata INCLUDING DEFAULTS
+ ) PARTITION BY LIST(partition_id);
+
+ ALTER TABLE _test_p_ci_jobs_metadata
+ ADD CONSTRAINT _test_p_ci_jobs_metadata_id_partition_id
+ UNIQUE (id, partition_id);
+
+ ALTER TABLE _test_p_ci_jobs_metadata
+ ATTACH PARTITION _test_ci_jobs_metadata FOR VALUES IN (1);
+
+ CREATE TABLE _test_ci_jobs(id serial NOT NULL PRIMARY KEY);
+ SQL
+
+ stub_const('Ci::Partitionable::Testing::PARTITIONABLE_MODELS', [model.name])
+
+ model.include(Ci::Partitionable)
+
+ model.partitionable scope: ->(r) { 1 },
+ through: { table: :_test_p_ci_jobs_metadata, flag: table_rollout_flag }
+
+ model.belongs_to :job, anonymous_class: jobs_model
+
+ jobs_model.has_one :metadata, anonymous_class: model,
+ foreign_key: :job_id, inverse_of: :job,
+ dependent: :destroy
+
+ allow(Feature::Definition).to receive(:get).and_call_original
+ allow(Feature::Definition).to receive(:get).with(table_rollout_flag)
+ .and_return(
+ Feature::Definition.new("development/#{table_rollout_flag}.yml",
+ { type: 'development', name: table_rollout_flag }
+ )
+ )
+ end
+
+ it { expect(model).not_to be_routing_class }
+
+ it { expect(partitioned_model).to be_routing_class }
+
+ it { expect(partitioned_model.table_name).to eq('_test_p_ci_jobs_metadata') }
+
+ it { expect(partitioned_model.quoted_table_name).to eq('"_test_p_ci_jobs_metadata"') }
+
+ it { expect(partitioned_model.arel_table.name).to eq('_test_p_ci_jobs_metadata') }
+
+ it { expect(partitioned_model.sequence_name).to eq('_test_ci_jobs_metadata_id_seq') }
+
+ context 'when switching the tables' do
+ before do
+ stub_feature_flags(table_rollout_flag => false)
+ end
+
+ %i[table_name quoted_table_name arel_table predicate_builder].each do |name|
+ it "switches #{name} to routing table and rollbacks" do
+ old_value = model.public_send(name)
+ routing_value = partitioned_model.public_send(name)
+
+ expect(old_value).not_to eq(routing_value)
+
+ expect { stub_feature_flags(table_rollout_flag => true) }
+ .to change(model, name).from(old_value).to(routing_value)
+
+ expect { stub_feature_flags(table_rollout_flag => false) }
+ .to change(model, name).from(routing_value).to(old_value)
+ end
+ end
+
+ it 'can switch aggregate methods' do
+ rollout_and_rollback_flag(
+ -> { expect(sql { model.count }).to all match(/FROM "_test_ci_jobs_metadata"/) },
+ -> { expect(sql { model.count }).to all match(/FROM "_test_p_ci_jobs_metadata"/) }
+ )
+ end
+
+ it 'can switch reads' do
+ rollout_and_rollback_flag(
+ -> { expect(sql { model.last }).to all match(/FROM "_test_ci_jobs_metadata"/) },
+ -> { expect(sql { model.last }).to all match(/FROM "_test_p_ci_jobs_metadata"/) }
+ )
+ end
+
+ it 'can switch inserts' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT/) { model.create! })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /INSERT/) { model.create! })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch deletes' do
+ 3.times { model.create! }
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /DELETE/) { model.last.destroy! })
+ .to all match(/DELETE FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /DELETE/) { model.last.destroy! })
+ .to all match(/DELETE FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ context 'with associations' do
+ let(:job) { jobs_model.create! }
+
+ it 'reads' do
+ model.create!(job_id: job.id)
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.find(job.id).metadata })
+ .to all match(/FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.find(job.id).metadata })
+ .to all match(/FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'writes' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.find(job.id).create_metadata! })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.find(job.id).create_metadata! })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'deletes' do
+ 3.times do
+ job = jobs_model.create!
+ job.create_metadata!
+ end
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /DELETE .* jobs_metadata/) { jobs_model.last.destroy! })
+ .to all match(/DELETE FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /DELETE .* jobs_metadata/) { jobs_model.last.destroy! })
+ .to all match(/DELETE FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch joins from jobs' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql { jobs_model.joins(:metadata).last })
+ .to all match(/INNER JOIN "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql { jobs_model.joins(:metadata).last })
+ .to all match(/INNER JOIN "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch joins from metadata' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql { model.joins(:job).last })
+ .to all match(/FROM "_test_ci_jobs_metadata" INNER JOIN "_test_ci_jobs"/)
+ },
+ -> {
+ expect(sql { model.joins(:job).last })
+ .to all match(/FROM "_test_p_ci_jobs_metadata" INNER JOIN "_test_ci_jobs"/)
+ }
+ )
+ end
+
+ it 'preloads' do
+ job = jobs_model.create!
+ job.create_metadata!
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.preload(:metadata).last })
+ .to all match(/FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.preload(:metadata).last })
+ .to all match(/FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ context 'with nested attributes' do
+ before do
+ jobs_model.accepts_nested_attributes_for :metadata
+ end
+
+ it 'writes' do
+ attrs = { metadata_attributes: { expanded_environment_name: 'test_env_name' } }
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.create!(attrs) })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata" .* 'test_env_name'/)
+ },
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.create!(attrs) })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata" .* 'test_env_name'/)
+ }
+ )
+ end
+ end
+ end
+ end
+
+ def rollout_and_rollback_flag(old, new)
+ # Load class and SQL statements cache
+ old.call
+
+ stub_feature_flags(table_rollout_flag => true)
+
+ # Test switch
+ new.call
+
+ stub_feature_flags(table_rollout_flag => false)
+
+ # Test that it can switch back in the same process
+ old.call
+ end
+
+ def create_tables(table_sql)
+ Ci::ApplicationRecord.connection.execute(table_sql)
+ end
+
+ def sql(filter: nil, &block)
+ result = ActiveRecord::QueryRecorder.new(&block)
+ result = result.log
+
+ return result unless filter
+
+ result.select { |statement| statement.match?(filter) }
+ end
+end
diff --git a/spec/models/concerns/ci/partitionable_spec.rb b/spec/models/concerns/ci/partitionable_spec.rb
index d53501ccc3d..f3d33c971c7 100644
--- a/spec/models/concerns/ci/partitionable_spec.rb
+++ b/spec/models/concerns/ci/partitionable_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Ci::Partitionable do
- describe 'partitionable models inclusion' do
- let(:ci_model) { Class.new(Ci::ApplicationRecord) }
+ let(:ci_model) { Class.new(Ci::ApplicationRecord) }
+ describe 'partitionable models inclusion' do
subject { ci_model.include(described_class) }
it 'raises an exception' do
@@ -23,4 +23,21 @@ RSpec.describe Ci::Partitionable do
end
end
end
+
+ context 'with through options' do
+ before do
+ allow(ActiveSupport::DescendantsTracker).to receive(:store_inherited)
+ stub_const("#{described_class}::Testing::PARTITIONABLE_MODELS", [ci_model.name])
+
+ ci_model.include(described_class)
+ ci_model.partitionable scope: ->(r) { 1 },
+ through: { table: :_test_table_name, flag: :some_flag }
+ end
+
+ it { expect(ci_model.routing_table_name).to eq(:_test_table_name) }
+
+ it { expect(ci_model.routing_table_name_flag).to eq(:some_flag) }
+
+ it { expect(ci_model.ancestors).to include(described_class::Switch) }
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 8842a36f40a..43ec0559eb3 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -1055,6 +1055,22 @@ RSpec.describe Issuable do
end
end
+ describe '#supports_confidentiality?' do
+ where(:issuable_type, :supports_confidentiality) do
+ :issue | true
+ :incident | true
+ :merge_request | false
+ end
+
+ with_them do
+ let(:issuable) { build_stubbed(issuable_type) }
+
+ subject { issuable.supports_confidentiality? }
+
+ it { is_expected.to eq(supports_confidentiality) }
+ end
+ end
+
describe '#severity' do
subject { issuable.severity }
diff --git a/spec/models/concerns/pg_full_text_searchable_spec.rb b/spec/models/concerns/pg_full_text_searchable_spec.rb
index 3e42a3504ac..5a693f084e6 100644
--- a/spec/models/concerns/pg_full_text_searchable_spec.rb
+++ b/spec/models/concerns/pg_full_text_searchable_spec.rb
@@ -76,7 +76,7 @@ RSpec.describe PgFullTextSearchable do
end
describe '.pg_full_text_search' do
- let(:english) { model_class.create!(project: project, title: 'title', description: 'something english') }
+ let(:english) { model_class.create!(project: project, title: 'title', description: 'something description english') }
let(:with_accent) { model_class.create!(project: project, title: 'Jürgen', description: 'Ærøskøbing') }
let(:japanese) { model_class.create!(project: project, title: '日本語 title', description: 'another english description') }
@@ -90,8 +90,19 @@ RSpec.describe PgFullTextSearchable do
expect(model_class.pg_full_text_search('title english')).to contain_exactly(english, japanese)
end
+ it 'searches specified columns only' do
+ matching_object = model_class.create!(project: project, title: 'english', description: 'some description')
+ matching_object.update_search_data!
+
+ expect(model_class.pg_full_text_search('english', matched_columns: %w(title))).to contain_exactly(matching_object)
+ end
+
+ it 'uses prefix matching' do
+ expect(model_class.pg_full_text_search('tit eng')).to contain_exactly(english, japanese)
+ end
+
it 'searches for exact term with quotes' do
- expect(model_class.pg_full_text_search('"something english"')).to contain_exactly(english)
+ expect(model_class.pg_full_text_search('"description english"')).to contain_exactly(english)
end
it 'ignores accents' do
@@ -113,6 +124,16 @@ RSpec.describe PgFullTextSearchable do
expect(model_class.pg_full_text_search('gopher://gitlab.com/gitlab-org/gitlab')).to contain_exactly(with_url)
end
end
+
+ context 'when text has numbers preceded by a dash' do
+ let(:with_dash) { model_class.create!(project: project, title: 'issue with dash', description: 'ABC-123') }
+
+ it 'allows searching by numbers only' do
+ with_dash.update_search_data!
+
+ expect(model_class.pg_full_text_search('123')).to contain_exactly(with_dash)
+ end
+ end
end
describe '#update_search_data!' do