gitlab-org--gitlab-foss/spec/graphql/types/current_user_todos_type_spe...

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