585 lines
19 KiB
Ruby
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
|