gitlab-ce/spec/requests/api/graphql/user/contributed_projects_query_...

585 lines
19 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Getting contributedProjects of the user', feature_category: :groups_and_projects do
include GraphqlHelpers
let(:query) { graphql_query_for(:user, user_params, user_fields) }
let(:user_params) { { username: user.username } }
let(:user_fields) { 'contributedProjects { nodes { id } }' }
let_it_be(:user) { create(:user, :with_namespace) }
let_it_be(:current_user) { create(:user) }
let_it_be(:public_project) { create(:project, :public, name: 'foo') }
let_it_be(:private_project) { create(:project, :private, name: 'bar') }
let_it_be(:internal_project) { create(:project, :internal, name: 'baz') }
let_it_be(:personal_project) { create(:project, namespace: user.namespace, name: 'biz') }
let(:path) { %i[user contributed_projects nodes] }
before_all do
private_project.add_developer(user)
private_project.add_developer(current_user)
personal_project.add_developer(current_user)
travel_to(4.hours.from_now) { create(:push_event, project: private_project, author: user) }
travel_to(3.hours.from_now) { create(:push_event, project: internal_project, author: user) }
travel_to(2.hours.from_now) { create(:push_event, project: public_project, author: user) }
travel_to(2.hours.from_now) { create(:push_event, project: personal_project, author: user) }
end
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
end
context 'when all fields are requested' do
let(:user_fields) do
"contributedProjects { nodes {#{all_graphql_fields_for('Project', max_depth: 1,
excluded: ['productAnalyticsState'])} } }"
end
it 'avoids N+1 queries', :use_sql_query_cache, :clean_gitlab_redis_cache do
post_graphql(query, current_user: current_user)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
new_project = create(:project, :public, name: 'New project', path: 'new-project')
new_project.add_developer(user)
travel_to(4.hours.from_now) { create(:push_event, project: new_project, author: user) }
# There is an N+1 query related to custom roles - https://gitlab.com/gitlab-org/gitlab/-/issues/515675
# There is an N+1 query for duo_features_enabled cascading setting - https://gitlab.com/gitlab-org/gitlab/-/issues/442164
# There is an N+1 query related to pipelines - https://gitlab.com/gitlab-org/gitlab/-/issues/515677
expect do
post_graphql(query, current_user: current_user)
end.not_to exceed_all_query_limit(control).with_threshold(5)
end
end
describe 'sorting' do
let(:user_fields_with_sort) { "contributedProjects(sort: #{sort_parameter}) { nodes { id } }" }
let(:query_with_sort) { graphql_query_for(:user, user_params, user_fields_with_sort) }
context 'when sort parameter is not provided' do
it 'returns contributed projects in default order(LATEST_ACTIVITY_DESC)' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
context 'when sort parameter for id is provided' do
context 'when ID_ASC is provided' do
let(:sort_parameter) { 'ID_ASC' }
it 'returns contributed projects in id ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s
])
end
end
context 'when ID_DESC is provided' do
let(:sort_parameter) { 'ID_DESC' }
it 'returns contributed projects in id descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for name is provided' do
before_all do
public_project.update!(name: 'Project A')
internal_project.update!(name: 'Project B')
private_project.update!(name: 'Project C')
end
context 'when NAME_ASC is provided' do
let(:sort_parameter) { 'NAME_ASC' }
it 'returns contributed projects in name ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when NAME_DESC is provided' do
let(:sort_parameter) { 'NAME_DESC' }
it 'returns contributed projects in name descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for path is provided' do
before_all do
public_project.update!(path: 'Project-1')
internal_project.update!(path: 'Project-2')
private_project.update!(path: 'Project-3')
end
context 'when PATH_ASC is provided' do
let(:sort_parameter) { 'PATH_ASC' }
it 'returns contributed projects in path ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when PATH_DESC is provided' do
let(:sort_parameter) { 'PATH_DESC' }
it 'returns contributed projects in path descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for stars is provided' do
before_all do
public_project.update!(star_count: 10)
internal_project.update!(star_count: 20)
private_project.update!(star_count: 30)
end
context 'when STARS_ASC is provided' do
let(:sort_parameter) { 'STARS_ASC' }
it 'returns contributed projects in stars ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when STARS_DESC is provided' do
let(:sort_parameter) { 'STARS_DESC' }
it 'returns contributed projects in stars descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for latest activity is provided' do
context 'when LATEST_ACTIVITY_ASC is provided' do
let(:sort_parameter) { 'LATEST_ACTIVITY_ASC' }
it 'returns contributed projects in latest activity ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when LATEST_ACTIVITY_DESC is provided' do
let(:sort_parameter) { 'LATEST_ACTIVITY_DESC' }
it 'returns contributed projects in latest activity descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for created_at is provided' do
before_all do
public_project.update!(created_at: Time.current + 1.hour)
internal_project.update!(created_at: Time.current + 2.hours)
private_project.update!(created_at: Time.current + 3.hours)
end
context 'when CREATED_ASC is provided' do
let(:sort_parameter) { 'CREATED_ASC' }
it 'returns contributed projects in created_at ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when CREATED_DESC is provided' do
let(:sort_parameter) { 'CREATED_DESC' }
it 'returns contributed projects in created_at descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
context 'when sort parameter for updated_at is provided' do
before_all do
public_project.update!(updated_at: Time.current + 1.hour)
internal_project.update!(updated_at: Time.current + 2.hours)
private_project.update!(updated_at: Time.current + 3.hours)
end
context 'when UPDATED_ASC is provided' do
let(:sort_parameter) { 'UPDATED_ASC' }
it 'returns contributed projects in updated_at ascending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
])
end
end
context 'when UPDATED_DESC is provided' do
let(:sort_parameter) { 'UPDATED_DESC' }
it 'returns contributed projects in updated_at descending order' do
post_graphql(query_with_sort, current_user: current_user)
expect(graphql_data_at(*path).pluck('id')).to eq([
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
])
end
end
end
end
describe 'min_access_level' do
let_it_be(:project_with_owner_access) { create(:project, :private) }
let(:user_fields_with_min_access_level) do
"contributedProjects(minAccessLevel: #{min_access_level}) { nodes { id } }"
end
let(:query_with_min_access_level) { graphql_query_for(:user, user_params, user_fields_with_min_access_level) }
before_all do
project_with_owner_access.add_owner(user)
project_with_owner_access.add_owner(current_user)
travel_to(4.hours.from_now) { create(:push_event, project: project_with_owner_access, author: user) }
end
context 'when min_access_level is OWNER' do
let(:min_access_level) { :OWNER }
it 'returns only projects user has owner access to' do
post_graphql(query_with_min_access_level, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(a_graphql_entity_for(project_with_owner_access))
end
end
context 'when min_access_level is DEVELOPER' do
let(:min_access_level) { :DEVELOPER }
it 'returns only projects user has developer or higher access to' do
post_graphql(query_with_min_access_level, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(
a_graphql_entity_for(project_with_owner_access),
a_graphql_entity_for(private_project)
)
end
end
end
describe 'programming_language_name' do
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:repository_language) do
create(:repository_language, project: internal_project, programming_language: ruby, share: 1)
end
let(:query_with_programming_language_name) do
graphql_query_for(:user, user_params, 'contributedProjects(programmingLanguageName: "ruby") { nodes { id } }')
end
it 'returns only projects with ruby programming language' do
post_graphql(query_with_programming_language_name, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(
a_graphql_entity_for(internal_project)
)
end
end
describe 'search' do
let(:query_with_search) do
graphql_query_for(:user, user_params, 'contributedProjects(search: "foo") { nodes { id } }')
end
it 'returns only projects that match search query' do
post_graphql(query_with_search, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(
a_graphql_entity_for(public_project)
)
end
end
describe 'accessible' do
context 'when user profile is public' do
context 'when a logged in user with membership in the private project' do
it 'returns contributed projects with visibility to the logged in user' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(*path)).to contain_exactly(
a_graphql_entity_for(private_project),
a_graphql_entity_for(internal_project),
a_graphql_entity_for(public_project)
)
end
end
context 'when a logged in user with no visibility to the private project' do
let_it_be(:current_user_2) { create(:user) }
it 'returns contributed projects with visibility to the logged in user' do
post_graphql(query, current_user: current_user_2)
expect(graphql_data_at(*path)).to contain_exactly(
a_graphql_entity_for(internal_project),
a_graphql_entity_for(public_project)
)
end
end
context 'when an anonymous user' do
it 'returns nothing' do
post_graphql(query, current_user: nil)
expect(graphql_data_at(*path)).to be_nil
end
end
end
context 'when user profile is private' do
let(:user_params) { { username: private_user.username } }
let_it_be(:private_user) { create(:user, :private_profile) }
before_all do
private_project.add_developer(private_user)
private_project.add_developer(current_user)
create(:push_event, project: private_project, author: private_user)
create(:push_event, project: internal_project, author: private_user)
create(:push_event, project: public_project, author: private_user)
end
context 'when a logged in user' do
it 'returns no project' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(*path)).to be_empty
end
end
context 'when an anonymous user' do
it 'returns nothing' do
post_graphql(query, current_user: nil)
expect(graphql_data_at(*path)).to be_nil
end
end
context 'when a logged in user is the user' do
it 'returns the user\'s all contributed projects' do
post_graphql(query, current_user: private_user)
expect(graphql_data_at(*path)).to contain_exactly(
a_graphql_entity_for(private_project),
a_graphql_entity_for(internal_project),
a_graphql_entity_for(public_project)
)
end
end
end
end
context 'when include_personal argument is false' do
it 'does not include personal projects' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(
a_graphql_entity_for(private_project),
a_graphql_entity_for(internal_project),
a_graphql_entity_for(public_project)
)
end
end
context 'when include_personal argument is true' do
let(:query_with_include_personal) do
graphql_query_for(:user, user_params, 'contributedProjects(includePersonal: true) { nodes { id } }')
end
it 'includes personal projects' do
post_graphql(query_with_include_personal, current_user: current_user)
expect(graphql_data_at(*path))
.to contain_exactly(
a_graphql_entity_for(private_project),
a_graphql_entity_for(internal_project),
a_graphql_entity_for(public_project),
a_graphql_entity_for(personal_project)
)
end
end
describe 'sorting and pagination' do
let(:data_path) { [:user, :contributed_projects] }
def pagination_query(params)
graphql_query_for(:user, user_params, "contributedProjects(#{params}) { #{page_info} nodes { id } }")
end
context 'when sorting in latest activity ascending order' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :LATEST_ACTIVITY_ASC }
let(:first_param) { 1 }
let(:all_records) do
[
public_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
private_project.to_global_id.to_s
]
end
end
end
context 'when sorting in latest activity descending order' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :LATEST_ACTIVITY_DESC }
let(:first_param) { 1 }
let(:all_records) do
[
private_project.to_global_id.to_s,
internal_project.to_global_id.to_s,
public_project.to_global_id.to_s
]
end
end
end
end
context 'when requesting user permissions' do
let(:user_fields) do
<<~QUERY
contributedProjects {
nodes {
id
userPermissions {
readProject
removeProject
}
}
}
QUERY
end
it_behaves_like 'a working graphql query that returns data' do
before do
post_graphql(query, current_user: current_user)
end
it 'returns data', :aggregate_failures do
expect(graphql_errors).to be_nil
expect(graphql_data_at(:user, :contributed_projects, :nodes, 0, :user_permissions)).to eq({
'readProject' => true,
'removeProject' => false
})
end
end
it 'batches data', :request_store do
queries = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
access_check_queries = queries.occurrences_starting_with(/.*FROM "project_authorizations".*/)
expect(access_check_queries.values.sum).to eq(1)
end
end
end