diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 10:33:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 10:33:21 +0300 |
commit | 36a59d088eca61b834191dacea009677a96c052f (patch) | |
tree | e4f33972dab5d8ef79e3944a9f403035fceea43f /spec/graphql/types/current_user_todos_type_spec.rb | |
parent | a1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff) |
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'spec/graphql/types/current_user_todos_type_spec.rb')
-rw-r--r-- | spec/graphql/types/current_user_todos_type_spec.rb | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/spec/graphql/types/current_user_todos_type_spec.rb b/spec/graphql/types/current_user_todos_type_spec.rb index a0015e96788..4ce97e1c006 100644 --- a/spec/graphql/types/current_user_todos_type_spec.rb +++ b/spec/graphql/types/current_user_todos_type_spec.rb @@ -3,7 +3,214 @@ 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 |