Welcome to mirror list, hosted at ThFree Co, Russian Federation.

caching_array_resolver_spec.rb « concerns « resolvers « graphql « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: b6fe94a23125fc8171794352d765f62f3ee76eda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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