217 lines
6.5 KiB
Ruby
217 lines
6.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe GitlabSchema.types['CurrentUserTodos'] do
|
|
include GraphqlHelpers
|
|
|
|
specify { expect(described_class.graphql_name).to eq('CurrentUserTodos') }
|
|
|
|
specify { expect(described_class).to have_graphql_fields(:current_user_todos).only }
|
|
|
|
# Request store is necessary to prevent duplicate max-member-access lookups
|
|
describe '.current_user_todos', :request_store, :aggregate_failures do
|
|
let_it_be(:user) { create(:user) }
|
|
let_it_be(:project) { create(:project, :repository) }
|
|
let_it_be(:issue_a) { create(:issue, project: project) }
|
|
let_it_be(:issue_b) { create(:issue, project: project) }
|
|
|
|
let_it_be(:todo_a) { create(:todo, :pending, user: user, project: project, target: issue_a) }
|
|
let_it_be(:todo_b) { create(:todo, :done, user: user, project: project, target: issue_a) }
|
|
let_it_be(:todo_c) { create(:todo, :pending, user: user, project: project, target: issue_b) }
|
|
let_it_be(:todo_d) { create(:todo, :done, user: user, project: project, target: issue_b) }
|
|
|
|
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
|
let_it_be(:todo_e) { create(:todo, :pending, user: user, project: project, target: merge_request) }
|
|
|
|
let(:object_type) do
|
|
fresh_object_type('HasTodos').tap { _1.implements(Types::CurrentUserTodos) }
|
|
end
|
|
|
|
let(:id_enum) do
|
|
Class.new(Types::BaseEnum) do
|
|
graphql_name 'AorB'
|
|
|
|
value 'A'
|
|
value 'B'
|
|
end
|
|
end
|
|
|
|
let(:query_type) do
|
|
i_a = issue_a
|
|
i_b = issue_b
|
|
issue_id = id_enum
|
|
mr = merge_request
|
|
|
|
q = fresh_object_type('Query')
|
|
|
|
q.field :issue, null: false, type: object_type do
|
|
argument :id, type: issue_id, required: true
|
|
end
|
|
|
|
q.field :mr, null: false, type: object_type
|
|
|
|
q.define_method(:issue) do |id:|
|
|
case id
|
|
when 'A'
|
|
i_a
|
|
when 'B'
|
|
i_b
|
|
end
|
|
end
|
|
|
|
q.define_method(:mr) { mr }
|
|
|
|
q
|
|
end
|
|
|
|
let(:todo_fragment) do
|
|
<<-GQL
|
|
fragment todos on HasTodos {
|
|
todos: currentUserTodos {
|
|
nodes { id }
|
|
}
|
|
}
|
|
GQL
|
|
end
|
|
|
|
let(:base_query) do
|
|
<<-GQL
|
|
query {
|
|
issue(id: A) { ... todos }
|
|
}
|
|
#{todo_fragment}
|
|
GQL
|
|
end
|
|
|
|
let(:query_without_state_arguments) do
|
|
<<-GQL
|
|
query {
|
|
a: issue(id: A) {
|
|
... todos
|
|
}
|
|
b: issue(id: B) {
|
|
... todos
|
|
}
|
|
c: mr {
|
|
... todos
|
|
}
|
|
d: mr {
|
|
... todos
|
|
}
|
|
e: issue(id: A) {
|
|
... todos
|
|
}
|
|
}
|
|
|
|
#{todo_fragment}
|
|
GQL
|
|
end
|
|
|
|
let(:with_state_arguments) do
|
|
<<-GQL
|
|
query {
|
|
a: issue(id: A) {
|
|
todos: currentUserTodos(state: pending) { nodes { id } }
|
|
}
|
|
b: issue(id: B) {
|
|
todos: currentUserTodos(state: done) { nodes { id } }
|
|
}
|
|
c: mr {
|
|
... todos
|
|
}
|
|
}
|
|
|
|
#{todo_fragment}
|
|
GQL
|
|
end
|
|
|
|
before_all do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it 'batches todo lookups, linear in the number of target types/state arguments' do
|
|
# The baseline is 4 queries:
|
|
#
|
|
# When we batch queries, we see the following three groups of queries:
|
|
# # user authorization
|
|
# 1. SELECT "users".* FROM "users"
|
|
# INNER JOIN "project_authorizations"
|
|
# ON "users"."id" = "project_authorizations"."user_id"
|
|
# WHERE "project_authorizations"."project_id" = project_id
|
|
# AND "project_authorizations"."access_level" = 50
|
|
# 2. SELECT MAX("project_authorizations"."access_level") AS maximum_access_level,
|
|
# "project_authorizations"."user_id" AS project_authorizations_user_id
|
|
# FROM "project_authorizations"
|
|
# WHERE "project_authorizations"."project_id" = project_id
|
|
# AND "project_authorizations"."user_id" = user_id
|
|
# GROUP BY "project_authorizations"."user_id"
|
|
#
|
|
# # find todos for issues
|
|
# 1. SELECT "todos".* FROM "todos"
|
|
# WHERE "todos"."user_id" = user_id
|
|
# AND ("todos"."state" IN ('done','pending'))
|
|
# AND "todos"."target_id" IN (issue_a, issue_b)
|
|
# AND "todos"."target_type" = 'Issue' ORDER BY "todos"."id" DESC
|
|
#
|
|
# # find todos for merge_requests
|
|
# 1. SELECT "todos".* FROM "todos" WHERE "todos"."user_id" = user_id
|
|
# AND ("todos"."state" IN ('done','pending'))
|
|
# AND "todos"."target_id" = merge_request
|
|
# AND "todos"."target_type" = 'MergeRequest' ORDER BY "todos"."id" DESC
|
|
baseline = ActiveRecord::QueryRecorder.new do
|
|
execute_query(query_type, graphql: base_query)
|
|
end
|
|
|
|
expect do
|
|
execute_query(query_type, graphql: query_without_state_arguments)
|
|
end.not_to exceed_query_limit(baseline) # at present this is 3
|
|
|
|
expect do
|
|
execute_query(query_type, graphql: with_state_arguments)
|
|
end.not_to exceed_query_limit(baseline.count + 1)
|
|
end
|
|
|
|
it 'returns correct data' do
|
|
result = execute_query(query_type,
|
|
graphql: query_without_state_arguments,
|
|
raise_on_error: true).to_h
|
|
|
|
expect(result.dig('data', 'a', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_a),
|
|
a_graphql_entity_for(todo_b)
|
|
)
|
|
expect(result.dig('data', 'b', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_c),
|
|
a_graphql_entity_for(todo_d)
|
|
)
|
|
expect(result.dig('data', 'c', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_e)
|
|
)
|
|
expect(result.dig('data', 'd', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_e)
|
|
)
|
|
expect(result.dig('data', 'e', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_a),
|
|
a_graphql_entity_for(todo_b)
|
|
)
|
|
end
|
|
|
|
it 'returns correct data, when state arguments are supplied' do
|
|
result = execute_query(query_type,
|
|
raise_on_error: true,
|
|
graphql: with_state_arguments).to_h
|
|
|
|
expect(result.dig('data', 'a', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_a)
|
|
)
|
|
expect(result.dig('data', 'b', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_d)
|
|
)
|
|
expect(result.dig('data', 'c', 'todos', 'nodes')).to contain_exactly(
|
|
a_graphql_entity_for(todo_e)
|
|
)
|
|
end
|
|
end
|
|
end
|