2020-06-05 21:08:27 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-26 09:08:59 +00:00
|
|
|
RSpec.describe LooksAhead do
|
2020-06-05 21:08:27 +00:00
|
|
|
include GraphqlHelpers
|
|
|
|
|
|
|
|
let_it_be(:the_user) { create(:user) }
|
|
|
|
let_it_be(:label_a) { create(:label) }
|
|
|
|
let_it_be(:label_b) { create(:label) }
|
|
|
|
let_it_be(:issue_a) { create(:issue, author: the_user, labels: [label_a, label_b]) }
|
|
|
|
let_it_be(:issue_b) { create(:issue, author: the_user, labels: [label_a]) }
|
|
|
|
let_it_be(:issue_c) { create(:issue, author: the_user, labels: [label_b]) }
|
|
|
|
|
|
|
|
# Simplified schema to test lookahead
|
|
|
|
let_it_be(:schema) do
|
2020-11-27 12:09:14 +00:00
|
|
|
issues_resolver = Class.new(GraphQL::Schema::Resolver) do
|
2020-06-05 21:08:27 +00:00
|
|
|
include LooksAhead
|
|
|
|
|
|
|
|
def resolve_with_lookahead(**args)
|
|
|
|
apply_lookahead(object.issues)
|
|
|
|
end
|
|
|
|
|
|
|
|
def preloads
|
|
|
|
{ labels: [:labels] }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
label = Class.new(GraphQL::Schema::Object) do
|
|
|
|
graphql_name 'Label'
|
|
|
|
field :id, Integer, null: false
|
|
|
|
end
|
|
|
|
issue = Class.new(GraphQL::Schema::Object) do
|
|
|
|
graphql_name 'Issue'
|
|
|
|
field :title, String, null: true
|
|
|
|
field :labels, label.connection_type, null: true
|
|
|
|
end
|
|
|
|
user = Class.new(GraphQL::Schema::Object) do
|
|
|
|
graphql_name 'User'
|
|
|
|
field :name, String, null: true
|
2021-03-18 06:11:52 +00:00
|
|
|
field :issues, issue.connection_type, null: true
|
|
|
|
field :issues_with_lookahead, issue.connection_type, resolver: issues_resolver, null: true
|
2020-06-05 21:08:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
Class.new(GraphQL::Schema) do
|
|
|
|
query(Class.new(GraphQL::Schema::Object) do
|
|
|
|
graphql_name 'Query'
|
|
|
|
field :find_user, user, null: true do
|
|
|
|
argument :username, String, required: true
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_user(username:)
|
|
|
|
context[:user_db].find { |u| u.username == username }
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def query(doc = document)
|
|
|
|
GraphQL::Query.new(schema,
|
|
|
|
document: doc,
|
|
|
|
context: { user_db: [the_user] },
|
|
|
|
variables: { username: the_user.username })
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:document) do
|
|
|
|
GraphQL.parse <<-GRAPHQL
|
|
|
|
query($username: String!){
|
|
|
|
findUser(username: $username) {
|
|
|
|
name
|
|
|
|
issues {
|
|
|
|
nodes {
|
|
|
|
title
|
|
|
|
labels { nodes { id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
issuesWithLookahead {
|
|
|
|
nodes {
|
|
|
|
title
|
|
|
|
labels { nodes { id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
GRAPHQL
|
|
|
|
end
|
|
|
|
|
|
|
|
def run_query(gql_query)
|
|
|
|
query(GraphQL.parse(gql_query)).result
|
|
|
|
end
|
|
|
|
|
|
|
|
shared_examples 'a working query on the test schema' do
|
|
|
|
it 'has a good test setup', :aggregate_failures do
|
|
|
|
expected_label_ids = [label_a, label_b].cycle.take(4).map(&:id)
|
|
|
|
issue_titles = [issue_a, issue_b, issue_c].map(&:title)
|
|
|
|
|
|
|
|
res = query.result
|
|
|
|
|
|
|
|
expect(res['errors']).to be_blank
|
|
|
|
expect(res.dig('data', 'findUser', 'name')).to eq(the_user.name)
|
2021-03-18 06:11:52 +00:00
|
|
|
%w[issues issuesWithLookahead].each do |field|
|
2020-06-05 21:08:27 +00:00
|
|
|
expect(all_issue_titles(res, field)).to match_array(issue_titles)
|
|
|
|
expect(all_label_ids(res, field)).to match_array(expected_label_ids)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'a working query on the test schema'
|
|
|
|
|
|
|
|
it 'preloads labels on issues' do
|
|
|
|
expect(the_user.issues).to receive(:preload).with(:labels)
|
|
|
|
|
|
|
|
query.result
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'issues fewer queries than the naive approach' do
|
|
|
|
the_user.reload # ensure no attributes are loaded before we begin
|
|
|
|
naive = <<-GQL
|
|
|
|
query($username: String!){
|
|
|
|
findUser(username: $username) {
|
|
|
|
name
|
|
|
|
issues {
|
|
|
|
nodes {
|
|
|
|
labels { nodes { id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
GQL
|
|
|
|
with_lookahead = <<-GQL
|
|
|
|
query($username: String!){
|
|
|
|
findUser(username: $username) {
|
|
|
|
name
|
|
|
|
issuesWithLookahead {
|
|
|
|
nodes {
|
|
|
|
labels { nodes { id } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
GQL
|
|
|
|
|
|
|
|
expect { run_query(with_lookahead) }.to issue_fewer_queries_than { run_query(naive) }
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def all_label_ids(result, field_name)
|
|
|
|
result.dig('data', 'findUser', field_name, 'nodes').flat_map do |node|
|
|
|
|
node.dig('labels', 'nodes').map { |n| n['id'] }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def all_issue_titles(result, field_name)
|
|
|
|
result.dig('data', 'findUser', field_name, 'nodes').map do |node|
|
|
|
|
node['title']
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|