Commit 6eac7ebd authored by Brett Walker's avatar Brett Walker Committed by Michael Kozono

Refactor GraphQL sorting and pagination specs

for issues into shared_examples
parent e934aee7
......@@ -5,98 +5,46 @@ require 'spec_helper'
describe 'getting an issue list for a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
let(:current_user) { create(:user) }
let(:issues_data) { graphql_data['project']['issues']['edges'] }
let_it_be(:current_user) { create(:user) }
describe 'sorting and pagination' do
let(:start_cursor) { graphql_data['project']['issues']['pageInfo']['startCursor'] }
let(:end_cursor) { graphql_data['project']['issues']['pageInfo']['endCursor'] }
let(:sort_project) { create(:project, :public) }
let(:data_path) { [:project, :issues] }
def pagination_query(params, page_info)
graphql_query_for(
'project',
{ 'fullPath' => sort_project.full_path },
"issues(#{params}) { #{page_info} edges { node { iid weight } } }"
)
end
context 'when sorting by weight' do
let(:sort_project) { create(:project, :public) }
def pagination_results_data(data)
data.map { |issue| issue.dig('node', 'iid').to_i }
end
context 'when sorting by weight' do
let!(:weight_issue1) { create(:issue, project: sort_project, weight: 5) }
let!(:weight_issue2) { create(:issue, project: sort_project, weight: nil) }
let!(:weight_issue3) { create(:issue, project: sort_project, weight: 1) }
let!(:weight_issue4) { create(:issue, project: sort_project, weight: nil) }
let!(:weight_issue5) { create(:issue, project: sort_project, weight: 3) }
let(:params) { 'sort: WEIGHT_ASC' }
def query(issue_params = params)
graphql_query_for(
'project',
{ 'fullPath' => sort_project.full_path },
<<~ISSUES
issues(#{issue_params}) {
pageInfo {
endCursor
}
edges {
node {
iid
weight
}
}
}
ISSUES
)
end
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
context 'when ascending' do
it 'sorts issues' do
expect(grab_iids).to eq [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid]
end
context 'when paginating' do
let(:params) { 'sort: WEIGHT_ASC, first: 2' }
it 'sorts issues' do
expect(grab_iids).to eq [weight_issue3.iid, weight_issue5.iid]
cursored_query = query("sort: WEIGHT_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq [weight_issue1.iid, weight_issue4.iid, weight_issue2.iid]
end
it_behaves_like 'sorted paginated query' do
let(:sort_param) { 'WEIGHT_ASC' }
let(:first_param) { 2 }
let(:expected_results) { [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid] }
end
end
context 'when descending' do
let(:params) { 'sort: WEIGHT_DESC' }
it 'sorts issues' do
expect(grab_iids).to eq [weight_issue1.iid, weight_issue5.iid, weight_issue3.iid, weight_issue4.iid, weight_issue2.iid]
end
context 'when paginating' do
let(:params) { 'sort: WEIGHT_DESC, first: 2' }
it 'sorts issues' do
expect(grab_iids).to eq [weight_issue1.iid, weight_issue5.iid]
cursored_query = query("sort: WEIGHT_DESC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq [weight_issue3.iid, weight_issue4.iid, weight_issue2.iid]
end
it_behaves_like 'sorted paginated query' do
let(:sort_param) { 'WEIGHT_DESC' }
let(:first_param) { 2 }
let(:expected_results) { [weight_issue1.iid, weight_issue5.iid, weight_issue3.iid, weight_issue4.iid, weight_issue2.iid] }
end
end
end
end
def grab_iids(data = issues_data)
data.map do |issue|
issue.dig('node', 'iid').to_i
end
end
end
......@@ -246,12 +246,19 @@ module GraphqlHelpers
# Raises an error if no data is found
def graphql_data
# Note that `json_response` is defined as `let(:json_response)` and
# therefore, in a spec with multiple queries, will only contain data
# from the _first_ query, not subsequent ones
json_response['data'] || (raise NoData, graphql_errors)
end
def graphql_data_at(*path)
graphql_dig_at(graphql_data, *path)
end
def graphql_dig_at(data, *path)
keys = path.map { |segment| GraphqlHelpers.fieldnamerize(segment) }
graphql_data.dig(*keys)
data.dig(*keys)
end
def graphql_errors
......
# frozen_string_literal: true
# Use this for testing how a GraphQL query handles sorting and pagination.
# This is particularly important when using keyset pagination connection,
# which is the default for ActiveRecord relations, as certain sort keys
# might not be supportable.
#
# sort_param: the value to specify the sort
# data_path: the keys necessary to dig into the return GraphQL data to get the
# returned results
# first_param: number of items expected (like a page size)
# expected_results: array of comparison data of all items sorted correctly
# pagination_query: method that specifies the GraphQL query
# pagination_results_data: method that extracts the sorted data used to compare against
# the expected results
#
# Example:
# describe 'sorting and pagination' do
# let(:sort_project) { create(:project, :public) }
# let(:data_path) { [:project, :issues] }
#
# def pagination_query(params, page_info)
# graphql_query_for(
# 'project',
# { 'fullPath' => sort_project.full_path },
# "issues(#{params}) { #{page_info} edges { node { iid weight } } }"
# )
# end
#
# def pagination_results_data(data)
# data.map { |issue| issue.dig('node', 'iid').to_i }
# end
#
# context 'when sorting by weight' do
# ...
# context 'when ascending' do
# it_behaves_like 'sorted paginated query' do
# let(:sort_param) { 'WEIGHT_ASC' }
# let(:first_param) { 2 }
# let(:expected_results) { [weight_issue3.iid, weight_issue5.iid, weight_issue1.iid, weight_issue4.iid, weight_issue2.iid] }
# end
# end
#
RSpec.shared_examples 'sorted paginated query' do
it_behaves_like 'requires variables' do
let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] }
end
describe do
let(:params) { "sort: #{sort_param}" }
let(:start_cursor) { graphql_data_at(*data_path, :pageInfo, :startCursor) }
let(:end_cursor) { graphql_data_at(*data_path, :pageInfo, :endCursor) }
let(:sorted_edges) { graphql_data_at(*data_path, :edges) }
let(:page_info) { "pageInfo { startCursor endCursor }" }
def pagination_query(params, page_info)
raise('pagination_query(params, page_info) must be defined in the test, see example in comment') unless defined?(super)
super
end
def pagination_results_data(data)
raise('pagination_results_data(data) must be defined in the test, see example in comment') unless defined?(super)
super(data)
end
before do
post_graphql(pagination_query(params, page_info), current_user: current_user)
end
context 'when sorting' do
it 'sorts correctly' do
expect(pagination_results_data(sorted_edges)).to eq expected_results
end
context 'when paginating' do
let(:params) { "sort: #{sort_param}, first: #{first_param}" }
it 'paginates correctly' do
expect(pagination_results_data(sorted_edges)).to eq expected_results.first(first_param)
cursored_query = pagination_query("sort: #{sort_param}, after: \"#{end_cursor}\"", page_info)
post_graphql(cursored_query, current_user: current_user)
response_data = graphql_dig_at(JSON.parse(response.body), :data, *data_path, :edges)
expect(pagination_results_data(response_data)).to eq expected_results.drop(first_param)
end
end
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'requires variables' do
it 'shared example requires variables to be set', :aggregate_failures do
variables = Array.wrap(required_variables)
variables.each do |variable_name|
expect { send(variable_name) }.not_to(
raise_error, "The following variable must be set to use this shared example: #{variable_name}"
)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment