diff options
Diffstat (limited to 'spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb')
-rw-r--r-- | spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb new file mode 100644 index 00000000000..b6fe94a2312 --- /dev/null +++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::CachingArrayResolver do + include GraphqlHelpers + + let_it_be(:non_admins) { create_list(:user, 4, admin: false) } + let(:query_context) { {} } + let(:max_page_size) { 10 } + let(:field) { double('Field', max_page_size: max_page_size) } + let(:schema) { double('Schema', default_max_page_size: 3) } + + let_it_be(:caching_resolver) do + mod = described_class + + Class.new(::Resolvers::BaseResolver) do + include mod + + 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 + + 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) }, field: field, schema: schema) + end + + expect(results.flat_map(&method(:force))).to match_array(users) + end + end + + context 'all queries return results' do + let_it_be(:admins) { create_list(:admin, 3) } + + it 'batches the queries' do + expect do + [resolve_users(true), resolve_users(false)].each(&method(:force)) + end.to issue_same_number_of_queries_as { force(resolve_users(nil)) } + end + + it 'finds the correct values' do + found_admins = resolve_users(true) + found_others = resolve_users(false) + admins_again = resolve_users(true) + found_all = resolve_users(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(false), resolve_users(false)].each(&method(:force)) + end + + context 'one of the queries returns no results' do + it 'finds the correct values' do + found_admins = resolve_users(true) + found_others = resolve_users(false) + found_all = resolve_users(nil) + + expect(force(found_admins)).to be_empty + expect(force(found_others)).to match_array(non_admins) + expect(force(found_all)).to match_array(non_admins) + end + end + + context 'one of the queries has already been cached' do + before do + force(resolve_users(nil)) + end + + it 'avoids further queries' do + expect do + repeated_find = resolve_users(nil) + + expect(force(repeated_find)).to match_array(non_admins) + end.not_to exceed_query_limit(0) + end + end + + context 'the resolver overrides item_found' do + let_it_be(:admins) { create_list(:admin, 2) } + 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(true, with_item_found) + found_all = resolve_users(nil, with_item_found) + + [found_admins, found_all].each(&method(:force)) + + expect(query_context[:found]).to match({ + false => be_empty, + true => match_array(admins), + 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(nil) + found_others = resolve_users(false) + + expect(force(found_all).size).to eq(2) + expect(force(found_others).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(nil) + found_others = resolve_users(false) + + expect(force(found_all).size).to eq(schema.default_max_page_size) + expect(force(found_others).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(is_admin, resolver = caching_resolver) + args = { is_admin: is_admin } + resolve(resolver, args: args, field: field, ctx: query_context, schema: schema) + end + + def force(lazy) + ::Gitlab::Graphql::Lazy.force(lazy) + end +end |