debian-mirror-gitlab/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb

312 lines
11 KiB
Ruby
Raw Normal View History

2019-12-26 22:10:19 +05:30
# frozen_string_literal: true
require 'spec_helper'
2020-04-22 19:07:51 +05:30
describe Gitlab::Graphql::Pagination::Keyset::Connection do
2019-12-26 22:10:19 +05:30
let(:nodes) { Project.all.order(id: :asc) }
let(:arguments) { {} }
2020-04-22 19:07:51 +05:30
let(:query_type) { GraphQL::ObjectType.new }
let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
2020-01-01 13:55:28 +05:30
2019-12-26 22:10:19 +05:30
subject(:connection) do
2020-04-22 19:07:51 +05:30
described_class.new(nodes, { context: context, max_page_size: 3 }.merge(arguments))
2019-12-26 22:10:19 +05:30
end
def encoded_cursor(node)
2020-04-22 19:07:51 +05:30
described_class.new(nodes, { context: context }).cursor_for(node)
2019-12-26 22:10:19 +05:30
end
def decoded_cursor(cursor)
JSON.parse(Base64Bp.urlsafe_decode64(cursor))
end
2020-04-22 19:07:51 +05:30
describe '#cursor_for' do
2019-12-26 22:10:19 +05:30
let(:project) { create(:project) }
2020-04-22 19:07:51 +05:30
let(:cursor) { connection.cursor_for(project) }
2019-12-26 22:10:19 +05:30
it 'returns an encoded ID' do
expect(decoded_cursor(cursor)).to eq('id' => project.id.to_s)
end
context 'when an order is specified' do
let(:nodes) { Project.order(:updated_at) }
it 'returns the encoded value of the order' do
expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s)
end
it 'includes the :id even when not specified in the order' do
expect(decoded_cursor(cursor)).to include('id' => project.id.to_s)
end
end
context 'when multiple orders are specified' do
let(:nodes) { Project.order(:updated_at).order(:created_at) }
it 'returns the encoded value of the order' do
expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s)
end
end
context 'when multiple orders with SQL are specified' do
let(:nodes) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) }
it 'returns the encoded value of the order' do
expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s)
end
end
end
describe '#sliced_nodes' do
let(:projects) { create_list(:project, 4) }
context 'when before is passed' do
let(:arguments) { { before: encoded_cursor(projects[1]) } }
it 'only returns the project before the selected one' do
expect(subject.sliced_nodes).to contain_exactly(projects.first)
end
context 'when the sort order is descending' do
let(:nodes) { Project.all.order(id: :desc) }
it 'returns the correct nodes' do
expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1])
end
end
end
context 'when after is passed' do
let(:arguments) { { after: encoded_cursor(projects[1]) } }
it 'only returns the project before the selected one' do
expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1])
end
context 'when the sort order is descending' do
let(:nodes) { Project.all.order(id: :desc) }
it 'returns the correct nodes' do
expect(subject.sliced_nodes).to contain_exactly(projects.first)
end
end
end
context 'when both before and after are passed' do
let(:arguments) do
{
after: encoded_cursor(projects[1]),
before: encoded_cursor(projects[3])
}
end
it 'returns the expected set' do
expect(subject.sliced_nodes).to contain_exactly(projects[2])
end
end
2020-03-13 15:44:24 +05:30
shared_examples 'nodes are in ascending order' do
context 'when no cursor is passed' do
let(:arguments) { {} }
it 'returns projects in ascending order' do
expect(subject.sliced_nodes).to eq(ascending_nodes)
end
end
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(ascending_nodes[2]) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes.first(2))
end
end
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(ascending_nodes[1]) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes.last(3))
end
end
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(ascending_nodes.last), after: encoded_cursor(ascending_nodes.first) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes[1..3])
end
end
end
shared_examples 'nodes are in descending order' do
context 'when no cursor is passed' do
let(:arguments) { {} }
it 'only returns projects in descending order' do
expect(subject.sliced_nodes).to eq(descending_nodes)
end
end
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(descending_nodes[2]) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes.first(2))
end
end
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(descending_nodes[1]) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes.last(3))
end
end
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(descending_nodes.last), after: encoded_cursor(descending_nodes.first) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes[1..3])
end
end
end
context 'when multiple orders with nil values are defined' do
2019-12-26 22:10:19 +05:30
let!(:project1) { create(:project, last_repository_check_at: 10.days.ago) } # Asc: project5 Desc: project3
let!(:project2) { create(:project, last_repository_check_at: nil) } # Asc: project1 Desc: project1
let!(:project3) { create(:project, last_repository_check_at: 5.days.ago) } # Asc: project3 Desc: project5
let!(:project4) { create(:project, last_repository_check_at: nil) } # Asc: project2 Desc: project2
let!(:project5) { create(:project, last_repository_check_at: 20.days.ago) } # Asc: project4 Desc: project4
context 'when ascending' do
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :asc).order(id: :asc)
end
2020-03-13 15:44:24 +05:30
let(:ascending_nodes) { [project5, project1, project3, project2, project4] }
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
it_behaves_like 'nodes are in ascending order'
2019-12-26 22:10:19 +05:30
context 'when before cursor value is NULL' do
let(:arguments) { { before: encoded_cursor(project4) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq([project5, project1, project3, project2])
end
end
context 'when after cursor value is NULL' do
let(:arguments) { { after: encoded_cursor(project2) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project4])
end
end
end
context 'when descending' do
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :desc).order(id: :asc)
end
2020-03-13 15:44:24 +05:30
let(:descending_nodes) { [project3, project1, project5, project2, project4] }
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
it_behaves_like 'nodes are in descending order'
2019-12-26 22:10:19 +05:30
context 'when before cursor value is NULL' do
let(:arguments) { { before: encoded_cursor(project4) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq([project3, project1, project5, project2])
end
end
context 'when after cursor value is NULL' do
let(:arguments) { { after: encoded_cursor(project2) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project4])
end
end
2020-03-13 15:44:24 +05:30
end
end
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
context 'when ordering uses LOWER' do
let!(:project1) { create(:project, name: 'A') } # Asc: project1 Desc: project4
let!(:project2) { create(:project, name: 'c') } # Asc: project5 Desc: project2
let!(:project3) { create(:project, name: 'b') } # Asc: project3 Desc: project3
let!(:project4) { create(:project, name: 'd') } # Asc: project2 Desc: project5
let!(:project5) { create(:project, name: 'a') } # Asc: project4 Desc: project1
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
context 'when ascending' do
let(:nodes) do
Project.order(Arel::Table.new(:projects)['name'].lower.asc).order(id: :asc)
2019-12-26 22:10:19 +05:30
end
2020-03-13 15:44:24 +05:30
let(:ascending_nodes) { [project1, project5, project3, project2, project4] }
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
it_behaves_like 'nodes are in ascending order'
end
2019-12-26 22:10:19 +05:30
2020-03-13 15:44:24 +05:30
context 'when descending' do
let(:nodes) do
Project.order(Arel::Table.new(:projects)['name'].lower.desc).order(id: :desc)
2019-12-26 22:10:19 +05:30
end
2020-03-13 15:44:24 +05:30
let(:descending_nodes) { [project4, project2, project3, project5, project1] }
it_behaves_like 'nodes are in descending order'
2019-12-26 22:10:19 +05:30
end
end
2020-01-01 13:55:28 +05:30
context 'when an invalid cursor is provided' do
let(:arguments) { { before: Base64Bp.urlsafe_encode64('invalidcursor', padding: false) } }
2019-12-26 22:10:19 +05:30
2020-01-01 13:55:28 +05:30
it 'raises an error' do
expect { subject.sliced_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
2019-12-26 22:10:19 +05:30
end
end
end
2020-04-22 19:07:51 +05:30
describe '#nodes' do
2019-12-26 22:10:19 +05:30
let_it_be(:all_nodes) { create_list(:project, 5) }
2020-04-22 19:07:51 +05:30
let(:paged_nodes) { subject.nodes }
2019-12-26 22:10:19 +05:30
2020-04-22 19:07:51 +05:30
it_behaves_like 'connection with paged nodes' do
2020-03-13 15:44:24 +05:30
let(:paged_nodes_size) { 3 }
end
2019-12-26 22:10:19 +05:30
context 'when both are passed' do
let(:arguments) { { first: 2, last: 2 } }
it 'raises an error' do
expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
end
context 'when primary key is not in original order' do
let(:nodes) { Project.order(last_repository_check_at: :desc) }
it 'is added to end' do
sliced = subject.sliced_nodes
last_order_name = sliced.order_values.last.expr.name
expect(last_order_name).to eq sliced.primary_key
end
end
context 'when there is no primary key' do
let(:nodes) { NoPrimaryKey.all }
it 'raises an error' do
expect(NoPrimaryKey.primary_key).to be_nil
expect { subject.sliced_nodes }.to raise_error(ArgumentError, 'Relation must have a primary key')
end
end
end
class NoPrimaryKey < ActiveRecord::Base
self.table_name = 'no_primary_key'
self.primary_key = nil
end
end