215 lines
6.3 KiB
Ruby
215 lines
6.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe ::CachingArrayResolver do
|
|
include GraphqlHelpers
|
|
include Gitlab::Graphql::Laziness
|
|
|
|
let_it_be(:admins) { create_list(:user, 4, admin: true) }
|
|
let(:query_context) { { current_user: admins.first } }
|
|
let(:max_page_size) { 10 }
|
|
let(:field) { double('Field', max_page_size: max_page_size) }
|
|
let(:schema) do
|
|
Class.new(GitlabSchema) do
|
|
default_max_page_size 3
|
|
end
|
|
end
|
|
|
|
let_it_be(:caching_resolver) do
|
|
mod = described_class
|
|
|
|
Class.new(::Resolvers::BaseResolver) do
|
|
include mod
|
|
type [::Types::UserType], null: true
|
|
argument :is_admin, ::GraphQL::BOOLEAN_TYPE, required: false
|
|
|
|
def query_input(is_admin:)
|
|
is_admin
|
|
end
|
|
|
|
def query_for(is_admin)
|
|
if is_admin.nil?
|
|
model_class.all
|
|
else
|
|
model_class.where(admin: is_admin)
|
|
end
|
|
end
|
|
|
|
def model_class
|
|
User # Happens to include FromUnion, and is cheap-ish to create
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#resolve' do
|
|
context 'there are more than MAX_UNION_SIZE queries' do
|
|
let_it_be(:max_union) { 3 }
|
|
let_it_be(:resolver) do
|
|
mod = described_class
|
|
max = max_union
|
|
|
|
Class.new(::Resolvers::BaseResolver) do
|
|
include mod
|
|
type [::Types::UserType], null: true
|
|
argument :username, ::GraphQL::STRING_TYPE, required: false
|
|
|
|
def query_input(username:)
|
|
username
|
|
end
|
|
|
|
def query_for(username)
|
|
if username.nil?
|
|
model_class.all
|
|
else
|
|
model_class.where(username: username)
|
|
end
|
|
end
|
|
|
|
def model_class
|
|
User # Happens to include FromUnion, and is cheap-ish to create
|
|
end
|
|
|
|
define_method :max_union_size do
|
|
max
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'executes the queries in multiple batches' do
|
|
users = create_list(:user, (max_union * 2) + 1)
|
|
expect(User).to receive(:from_union).twice.and_call_original
|
|
|
|
results = users.in_groups_of(2, false).map do |users|
|
|
resolve(resolver, args: { username: users.map(&:username) }, schema: schema)
|
|
end
|
|
|
|
expect(results.flat_map(&method(:force))).to match_array(users)
|
|
end
|
|
end
|
|
|
|
context 'all queries return results' do
|
|
let_it_be(:non_admins) { create_list(:user, 3, admin: false) }
|
|
|
|
it 'batches the queries' do
|
|
expect do
|
|
[resolve_users(admin: true), resolve_users(admin: false)].each(&method(:force))
|
|
end.to issue_same_number_of_queries_as { force(resolve_users(admin: nil)) }
|
|
end
|
|
|
|
it 'finds the correct values' do
|
|
found_admins = resolve_users(admin: true)
|
|
found_others = resolve_users(admin: false)
|
|
admins_again = resolve_users(admin: true)
|
|
found_all = resolve_users(admin: nil)
|
|
|
|
expect(force(found_admins)).to match_array(admins)
|
|
expect(force(found_others)).to match_array(non_admins)
|
|
expect(force(admins_again)).to match_array(admins)
|
|
expect(force(found_all)).to match_array(admins + non_admins)
|
|
end
|
|
end
|
|
|
|
it 'does not perform a union of a query with itself' do
|
|
expect(User).to receive(:where).once.and_call_original
|
|
|
|
[resolve_users(admin: false), resolve_users(admin: false)].each(&method(:force))
|
|
end
|
|
|
|
context 'one of the queries returns no results' do
|
|
it 'finds the correct values' do
|
|
found_admins = resolve_users(admin: true)
|
|
found_others = resolve_users(admin: false)
|
|
found_all = resolve_users(admin: nil)
|
|
|
|
expect(force(found_admins)).to match_array(admins)
|
|
expect(force(found_others)).to be_empty
|
|
expect(force(found_all)).to match_array(admins)
|
|
end
|
|
end
|
|
|
|
context 'one of the queries has already been cached' do
|
|
before do
|
|
force(resolve_users(admin: nil))
|
|
end
|
|
|
|
it 'avoids further queries' do
|
|
expect do
|
|
repeated_find = resolve_users(admin: nil)
|
|
|
|
expect(force(repeated_find)).to match_array(admins)
|
|
end.not_to exceed_query_limit(0)
|
|
end
|
|
end
|
|
|
|
context 'the resolver overrides item_found' do
|
|
let_it_be(:non_admins) { create_list(:user, 2, admin: false) }
|
|
let(:query_context) do
|
|
{
|
|
found: { true => [], false => [], nil => [] }
|
|
}
|
|
end
|
|
|
|
let_it_be(:with_item_found) do
|
|
Class.new(caching_resolver) do
|
|
def item_found(key, item)
|
|
context[:found][key] << item
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'receives item_found for each key the item mapped to' do
|
|
found_admins = resolve_users(admin: true, resolver: with_item_found)
|
|
found_all = resolve_users(admin: nil, resolver: with_item_found)
|
|
|
|
[found_admins, found_all].each(&method(:force))
|
|
|
|
expect(query_context[:found]).to match({
|
|
true => match_array(admins),
|
|
false => be_empty,
|
|
nil => match_array(admins + non_admins)
|
|
})
|
|
end
|
|
end
|
|
|
|
context 'the max_page_size is lower than the total result size' do
|
|
let(:max_page_size) { 2 }
|
|
|
|
it 'respects the max_page_size, on a per subset basis' do
|
|
found_all = resolve_users(admin: nil)
|
|
found_admins = resolve_users(admin: true)
|
|
|
|
expect(force(found_all).size).to eq(2)
|
|
expect(force(found_admins).size).to eq(2)
|
|
end
|
|
end
|
|
|
|
context 'the field does not declare max_page_size' do
|
|
let(:max_page_size) { nil }
|
|
|
|
it 'takes the page size from schema.default_max_page_size' do
|
|
found_all = resolve_users(admin: nil)
|
|
found_admins = resolve_users(admin: true)
|
|
|
|
expect(force(found_all).size).to eq(schema.default_max_page_size)
|
|
expect(force(found_admins).size).to eq(schema.default_max_page_size)
|
|
end
|
|
end
|
|
|
|
specify 'force . resolve === to_a . query_for . query_input' do
|
|
r = resolver_instance(caching_resolver)
|
|
args = { is_admin: false }
|
|
|
|
naive = r.query_for(r.query_input(**args)).to_a
|
|
|
|
expect(force(r.resolve(**args))).to eq(naive)
|
|
end
|
|
end
|
|
|
|
def resolve_users(admin:, resolver: caching_resolver)
|
|
args = { is_admin: admin }
|
|
opts = resolver.field_options
|
|
allow(resolver).to receive(:field_options).and_return(opts.merge(max_page_size: max_page_size))
|
|
resolve(resolver, args: args, ctx: query_context, schema: schema, field: field)
|
|
end
|
|
end
|