summaryrefslogtreecommitdiff
path: root/spec/models
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-10 00:10:04 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-10 00:10:04 +0000
commit1bf4fece121298260663c6ca73d39716d3548a03 (patch)
tree5c82dd2fa63552ecb67fcb034740ec7e8fdba25a /spec/models
parent254ec28f5448f6f353cd98f637985de3d1405854 (diff)
downloadgitlab-ce-1bf4fece121298260663c6ca73d39716d3548a03.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r--spec/models/concerns/where_composite_spec.rb184
-rw-r--r--spec/models/issue_spec.rb16
2 files changed, 200 insertions, 0 deletions
diff --git a/spec/models/concerns/where_composite_spec.rb b/spec/models/concerns/where_composite_spec.rb
new file mode 100644
index 00000000000..1c0951d90d0
--- /dev/null
+++ b/spec/models/concerns/where_composite_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe WhereComposite do
+ describe '.where_composite' do
+ let_it_be(:test_table_name) { "test_table_#{SecureRandom.hex(10)}" }
+
+ let(:model) do
+ tbl_name = test_table_name
+ Class.new(ActiveRecord::Base) do
+ self.table_name = tbl_name
+
+ include WhereComposite
+ end
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ before_all do
+ connection.create_table(test_table_name) do |t|
+ t.integer :foo
+ t.integer :bar
+ t.string :wibble
+ end
+ end
+
+ it 'requires at least one permitted key' do
+ expect { model.where_composite([], nil) }
+ .to raise_error(ArgumentError)
+ end
+
+ it 'requires all arguments to match the permitted_keys' do
+ expect { model.where_composite([:foo], [{ foo: 1 }, { bar: 2 }]) }
+ .to raise_error(ArgumentError)
+ end
+
+ it 'attaches a key error as cause if a key is missing' do
+ expect { model.where_composite([:foo], [{ foo: 1 }, { bar: 2 }]) }
+ .to raise_error(have_attributes(cause: KeyError))
+ end
+
+ it 'returns an empty relation if there are no arguments' do
+ expect(model.where_composite([:foo, :bar], nil))
+ .to be_empty
+
+ expect(model.where_composite([:foo, :bar], []))
+ .to be_empty
+ end
+
+ it 'permits extra arguments' do
+ a = model.where_composite([:foo, :bar], { foo: 1, bar: 2 })
+ b = model.where_composite([:foo, :bar], { foo: 1, bar: 2, baz: 3 })
+
+ expect(a.to_sql).to eq(b.to_sql)
+ end
+
+ it 'can handle multiple fields' do
+ fields = [:foo, :bar, :wibble]
+ args = { foo: 1, bar: 2, wibble: 'wobble' }
+ pattern = %r{
+ WHERE \s+
+ \(?
+ \s* "#{test_table_name}"\."foo" \s* = \s* 1
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 2
+ \s+ AND
+ \s+ "#{test_table_name}"\."wibble" \s* = \s* 'wobble'
+ \s*
+ \)?
+ }x
+
+ expect(model.where_composite(fields, args).to_sql).to match(pattern)
+ end
+
+ it 'is equivalent to ids.map { |attrs| model.find_by(attrs) }' do
+ 10.times do |i|
+ 10.times do |j|
+ model.create!(foo: i, bar: j, wibble: generate(:filename))
+ end
+ end
+
+ ids = [{ foo: 1, bar: 9 }, { foo: 9, bar: 1 }]
+
+ found = model.where_composite(%i[foo bar], ids)
+
+ expect(found).to match_array(ids.map { |attrs| model.find_by!(attrs) })
+ end
+
+ it 'constructs (A&B) for one argument' do
+ fields = [:foo, :bar]
+ args = [
+ { foo: 1, bar: 2 }
+ ]
+ pattern = %r{
+ WHERE \s+
+ \(?
+ \s* "#{test_table_name}"\."foo" \s* = \s* 1
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 2
+ \s*
+ \)?
+ }x
+
+ expect(model.where_composite(fields, args).to_sql).to match(pattern)
+ expect(model.where_composite(fields, args[0]).to_sql).to match(pattern)
+ end
+
+ it 'constructs (A&B) OR (C&D) for two arguments' do
+ args = [
+ { foo: 1, bar: 2 },
+ { foo: 3, bar: 4 }
+ ]
+ pattern = %r{
+ WHERE \s+
+ \( \s* "#{test_table_name}"\."foo" \s* = \s* 1
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 2
+ \s* \)?
+ \s* OR \s*
+ \(? \s* "#{test_table_name}"\."foo" \s* = \s* 3
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 4
+ \s* \)
+ }x
+
+ q = model.where_composite([:foo, :bar], args)
+
+ expect(q.to_sql).to match(pattern)
+ end
+
+ it 'constructs (A&B) OR (C&D) OR (E&F) for three arguments' do
+ args = [
+ { foo: 1, bar: 2 },
+ { foo: 3, bar: 4 },
+ { foo: 5, bar: 6 }
+ ]
+ pattern = %r{
+ WHERE \s+
+ \({2}
+ \s* "#{test_table_name}"\."foo" \s* = \s* 1
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 2
+ \s* \)?
+ \s* OR \s*
+ \(? \s* "#{test_table_name}"\."foo" \s* = \s* 3
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 4
+ \s* \)?
+ \s* OR \s*
+ \(? \s* "#{test_table_name}"\."foo" \s* = \s* 5
+ \s+ AND
+ \s+ "#{test_table_name}"\."bar" \s* = \s* 6
+ \s* \)
+ }x
+
+ q = model.where_composite([:foo, :bar], args)
+
+ expect(q.to_sql).to match(pattern)
+ end
+
+ describe 'large sets of IDs' do
+ def query(size)
+ args = (0..).lazy.take(size).map { |n| { foo: n, bar: n * n, wibble: 'x' * n } }.to_a
+ model.where_composite([:foo, :bar, :wibble], args)
+ end
+
+ it 'constructs correct trees of constraints' do
+ n = described_class::TooManyIds::LIMIT
+ q = query(n)
+ sql = q.to_sql
+
+ expect(sql.scan(/OR/).count).to eq(n - 1)
+ expect(sql.scan(/AND/).count).to eq(2 * n)
+ end
+
+ it 'raises errors if too many IDs are passed' do
+ expect { query(described_class::TooManyIds::LIMIT + 1) }.to raise_error(described_class::TooManyIds)
+ end
+ end
+ end
+end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 9797e0a0472..e8103be0682 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -900,6 +900,22 @@ describe Issue do
end
end
+ describe '.by_project_id_and_iid' do
+ let_it_be(:issue_a) { create(:issue) }
+ let_it_be(:issue_b) { create(:issue, iid: issue_a.iid) }
+ let_it_be(:issue_c) { create(:issue, project: issue_a.project) }
+ let_it_be(:issue_d) { create(:issue, project: issue_a.project) }
+
+ it_behaves_like 'a where_composite scope', :by_project_id_and_iid do
+ let(:all_results) { [issue_a, issue_b, issue_c, issue_d] }
+ let(:first_result) { issue_a }
+
+ let(:composite_ids) do
+ all_results.map { |issue| { project_id: issue.project_id, iid: issue.iid } }
+ end
+ end
+ end
+
it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) }
end