diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
commit | a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4 (patch) | |
tree | fb69158581673816a8cd895f9d352dcb3c678b1e /spec/graphql | |
parent | d16b2e8639e99961de6ddc93909f3bb5c1445ba1 (diff) | |
download | gitlab-ce-a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4.tar.gz |
Add latest changes from gitlab-org/gitlab@14-0-stable-eev14.0.0-rc42
Diffstat (limited to 'spec/graphql')
28 files changed, 632 insertions, 207 deletions
diff --git a/spec/graphql/mutations/ci/runner/delete_spec.rb b/spec/graphql/mutations/ci/runner/delete_spec.rb new file mode 100644 index 00000000000..82873c96c3e --- /dev/null +++ b/spec/graphql/mutations/ci/runner/delete_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::Runner::Delete do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:runner) { create(:ci_runner) } + + let(:current_ctx) { { current_user: user } } + + let(:mutation_params) do + { + id: runner.to_global_id + } + end + + specify { expect(described_class).to require_graphql_authorizations(:delete_runner) } + + describe '#resolve' do + subject do + sync(resolve(described_class, args: mutation_params, ctx: current_ctx)) + end + + context 'when the user cannot admin the runner' do + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with invalid params' do + it 'raises an error' do + mutation_params[:id] = "invalid-id" + + expect { subject }.to raise_error(::GraphQL::CoercionError) + end + end + + context 'when required arguments are missing' do + let(:mutation_params) { {} } + + it 'raises an error' do + expect { subject }.to raise_error(ArgumentError, "missing keyword: :id") + end + end + + context 'when user can delete owned runner' do + let_it_be(:project) { create(:project, creator_id: user.id) } + let_it_be(:project_runner, reload: true) { create(:ci_runner, :project, description: 'Project runner', projects: [project]) } + + before_all do + project.add_maintainer(user) + end + + context 'with one associated project' do + it 'deletes runner' do + mutation_params[:id] = project_runner.to_global_id + + expect { subject }.to change { Ci::Runner.count }.by(-1) + expect(subject[:errors]).to be_empty + end + end + + context 'with more than one associated project' do + let_it_be(:project2) { create(:project, creator_id: user.id) } + let_it_be(:two_projects_runner) { create(:ci_runner, :project, description: 'Two projects runner', projects: [project, project2]) } + + before_all do + project2.add_maintainer(user) + end + + it 'does not delete project runner' do + mutation_params[:id] = two_projects_runner.to_global_id + + expect { subject }.not_to change { Ci::Runner.count } + expect(subject[:errors]).to contain_exactly("Runner #{two_projects_runner.to_global_id} associated with more than one project") + end + end + end + + context 'when admin can delete runner', :enable_admin_mode do + let(:admin_user) { create(:user, :admin) } + let(:current_ctx) { { current_user: admin_user } } + + it 'deletes runner' do + expect { subject }.to change { Ci::Runner.count }.by(-1) + expect(subject[:errors]).to be_empty + end + end + end +end diff --git a/spec/graphql/mutations/ci/runner/update_spec.rb b/spec/graphql/mutations/ci/runner/update_spec.rb new file mode 100644 index 00000000000..3db0d552a05 --- /dev/null +++ b/spec/graphql/mutations/ci/runner/update_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::Runner::Update do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:runner) { create(:ci_runner, active: true, locked: false, run_untagged: true) } + + let(:current_ctx) { { current_user: user } } + let(:mutated_runner) { subject[:runner] } + + let(:mutation_params) do + { + id: runner.to_global_id, + description: 'updated description' + } + end + + specify { expect(described_class).to require_graphql_authorizations(:update_runner) } + + describe '#resolve' do + subject do + sync(resolve(described_class, args: mutation_params, ctx: current_ctx)) + end + + context 'when the user cannot admin the runner' do + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with invalid params' do + it 'raises an error' do + mutation_params[:id] = "invalid-id" + + expect { subject }.to raise_error(::GraphQL::CoercionError) + end + end + + context 'when required arguments are missing' do + let(:mutation_params) { {} } + + it 'raises an error' do + expect { subject }.to raise_error(ArgumentError, "missing keyword: :id") + end + end + + context 'when user can update runner', :enable_admin_mode do + let(:admin_user) { create(:user, :admin) } + let(:current_ctx) { { current_user: admin_user } } + + let(:mutation_params) do + { + id: runner.to_global_id, + description: 'updated description', + maximum_timeout: 900, + access_level: 'ref_protected', + active: false, + locked: true, + run_untagged: false, + tag_list: %w(tag1 tag2) + } + end + + context 'with valid arguments' do + it 'updates runner with correct values' do + expected_attributes = mutation_params.except(:id, :tag_list) + + subject + + expect(subject[:errors]).to be_empty + expect(subject[:runner]).to be_an_instance_of(Ci::Runner) + expect(subject[:runner]).to have_attributes(expected_attributes) + expect(subject[:runner].tag_list).to contain_exactly(*mutation_params[:tag_list]) + expect(runner.reload).to have_attributes(expected_attributes) + expect(runner.tag_list).to contain_exactly(*mutation_params[:tag_list]) + end + end + + context 'with out-of-range maximum_timeout and missing tag_list' do + it 'returns a descriptive error' do + mutation_params[:maximum_timeout] = 100 + mutation_params.delete(:tag_list) + + expect(subject[:errors]).to contain_exactly( + 'Maximum timeout needs to be at least 10 minutes', + 'Tags list can not be empty when runner is not allowed to pick untagged jobs' + ) + end + end + end + end +end diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index 152b5d87da0..097e70bada6 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -74,6 +74,10 @@ RSpec.describe Mutations::Commits::Create do expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+)) end + it 'returns the content of the commit' do + expect(subject[:content]).to eq(actions.pluck(:content)) + end + it 'returns a new commit' do expect(mutated_commit).to have_attributes(message: message, project: project) expect(subject[:errors]).to be_empty @@ -166,6 +170,7 @@ RSpec.describe Mutations::Commits::Create do it 'returns a new commit' do expect(mutated_commit).to have_attributes(message: message, project: project) expect(subject[:errors]).to be_empty + expect(subject[:content]).to eq(actions.pluck(:content)) expect_to_contain_deltas([ a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md') diff --git a/spec/graphql/mutations/design_management/upload_spec.rb b/spec/graphql/mutations/design_management/upload_spec.rb index ada88b7652c..1e585e55be4 100644 --- a/spec/graphql/mutations/design_management/upload_spec.rb +++ b/spec/graphql/mutations/design_management/upload_spec.rb @@ -105,7 +105,7 @@ RSpec.describe Mutations::DesignManagement::Upload do context "with a valid design" do it "returns the updated designs" do - expect(resolve[:errors]).to eq [] + expect(resolve[:errors]).to be_empty expect(resolve[:designs].map(&:filename)).to contain_exactly("dk.png") end end diff --git a/spec/graphql/mutations/issues/set_subscription_spec.rb b/spec/graphql/mutations/issues/set_subscription_spec.rb index 9e05a136c0b..7e2c3d93c51 100644 --- a/spec/graphql/mutations/issues/set_subscription_spec.rb +++ b/spec/graphql/mutations/issues/set_subscription_spec.rb @@ -3,8 +3,38 @@ require 'spec_helper' RSpec.describe Mutations::Issues::SetSubscription do - it_behaves_like 'a subscribeable graphql resource' do - let_it_be(:resource) { create(:issue) } - let(:permission_name) { :update_issue } + let_it_be_with_reload(:project) { create(:project) } + let_it_be_with_reload(:resource) { create(:issue, project: project) } + let_it_be(:user) { create(:user) } + + specify { expect(described_class).to require_graphql_authorizations(:update_subscription) } + + context 'when user does not have access to the project' do + it_behaves_like 'a subscribeable not accessible graphql resource' + end + + context 'when user is developer member of the project' do + before do + project.add_developer(user) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public but the issue is confidential' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + resource.update!(confidential: true) + end + + it_behaves_like 'a subscribeable not accessible graphql resource' end end diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index 6d6a5b94219..bd780477658 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -69,33 +69,17 @@ RSpec.describe Mutations::Issues::Update do context 'when changing state' do let_it_be_with_refind(:issue) { create(:issue, project: project, state: :opened) } - before do - mutation_params[:state_event] = state_event - end - - context 'when state_event is close' do - let_it_be(:removable_label) { create(:label, project: project, remove_on_close: true, issues: [issue]) } + it 'closes issue' do + mutation_params[:state_event] = 'close' - let(:state_event) { 'close' } - - it 'closes issue' do - expect do - subject - issue.reload - end.to change(issue, :state).from('opened').to('closed').and( - change { issue.label_ids }.from([removable_label.id]).to([]) - ) - end + expect { subject }.to change { issue.reload.state }.from('opened').to('closed') end - context 'when state_event is reopen' do - let(:state_event) { 'reopen' } - - it 'reopens issue' do - issue.close + it 'reopens issue' do + issue.close + mutation_params[:state_event] = 'reopen' - expect { subject }.to change { issue.reload.state }.from('closed').to('opened') - end + expect { subject }.to change { issue.reload.state }.from('closed').to('opened') end end diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb index 600053637c9..377042f068c 100644 --- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -3,8 +3,30 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::SetSubscription do - it_behaves_like 'a subscribeable graphql resource' do - let_it_be(:resource) { create(:merge_request) } - let(:permission_name) { :update_merge_request } + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + let(:resource) { create(:merge_request, source_project: project, target_project: project) } + + specify { expect(described_class).to require_graphql_authorizations(:update_subscription) } + + context 'when user does not have access to the project' do + it_behaves_like 'a subscribeable not accessible graphql resource' + end + + context 'when user is developer member of the project' do + before do + project.add_developer(user) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it_behaves_like 'a subscribeable graphql resource' end end diff --git a/spec/graphql/mutations/todos/mark_all_done_spec.rb b/spec/graphql/mutations/todos/mark_all_done_spec.rb index f3b6bf52ef7..1e12d86aa18 100644 --- a/spec/graphql/mutations/todos/mark_all_done_spec.rb +++ b/spec/graphql/mutations/todos/mark_all_done_spec.rb @@ -21,26 +21,25 @@ RSpec.describe Mutations::Todos::MarkAllDone do describe '#resolve' do it 'marks all pending todos as done' do - updated_todo_ids, todos = mutation_for(current_user).resolve.values_at(:updated_ids, :todos) + todos = mutation_for(current_user).resolve[:todos] expect(todo1.reload.state).to eq('done') expect(todo2.reload.state).to eq('done') expect(todo3.reload.state).to eq('done') expect(other_user_todo.reload.state).to eq('pending') - expect(updated_todo_ids).to contain_exactly(todo1.id, todo3.id) expect(todos).to contain_exactly(todo1, todo3) end it 'behaves as expected if there are no todos for the requesting user' do - updated_todo_ids = mutation_for(user3).resolve.dig(:updated_ids) + todos = mutation_for(user3).resolve[:todos] expect(todo1.reload.state).to eq('pending') expect(todo2.reload.state).to eq('done') expect(todo3.reload.state).to eq('pending') expect(other_user_todo.reload.state).to eq('pending') - expect(updated_todo_ids).to be_empty + expect(todos).to be_empty end context 'when user is not logged in' do diff --git a/spec/graphql/resolvers/boards_resolver_spec.rb b/spec/graphql/resolvers/boards_resolver_spec.rb index 221e905f441..07d0902d3ba 100644 --- a/spec/graphql/resolvers/boards_resolver_spec.rb +++ b/spec/graphql/resolvers/boards_resolver_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Resolvers::BoardsResolver do shared_examples_for 'group and project boards resolver' do it 'does not create a default board' do - expect(resolve_boards).to eq [] + expect(resolve_boards).to be_empty end it 'calls Boards::BoardsFinder' do diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index 006d6785506..5ac15d5729f 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -10,15 +10,15 @@ RSpec.describe Resolvers::Ci::RunnersResolver do let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:inactive_project_runner) do - create(:ci_runner, :project, projects: [project], active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner)) + create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner)) end let_it_be(:offline_project_runner) do - create(:ci_runner, :project, projects: [project], contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner)) + create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner)) end - let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], contacted_at: 1.second.ago) } - let_it_be(:instance_runner) { create(:ci_runner, :instance, contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) } + let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) } + let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) } describe '#resolve' do subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a } @@ -27,6 +27,14 @@ RSpec.describe Resolvers::Ci::RunnersResolver do {} end + context 'when the user cannot see runners' do + let(:user) { create(:user) } + + it 'returns no runners' do + is_expected.to be_empty + end + end + context 'without sort' do it 'returns all the runners' do is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner) @@ -42,13 +50,29 @@ RSpec.describe Resolvers::Ci::RunnersResolver do it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) } end - context "set to :created_date" do + context "set to :contacted_desc" do + let(:args) do + { sort: :contacted_desc } + end + + it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) } + end + + context "set to :created_at_desc" do let(:args) do - { sort: :created_date } + { sort: :created_at_desc } end it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) } end + + context "set to :created_at_asc" do + let(:args) do + { sort: :created_at_asc } + end + + it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) } + end end context 'when type is filtered' do @@ -132,5 +156,35 @@ RSpec.describe Resolvers::Ci::RunnersResolver do end end end + + context 'when text is filtered' do + let(:args) do + { search: search_term } + end + + context 'to "project"' do + let(:search_term) { 'project' } + + it 'returns both project runners' do + is_expected.to contain_exactly(inactive_project_runner, offline_project_runner) + end + end + + context 'to "group"' do + let(:search_term) { 'group' } + + it 'returns group runner' do + is_expected.to contain_exactly(group_runner) + end + end + + context 'to "defghi"' do + let(:search_term) { 'defghi' } + + it 'returns runners containing term in token' do + is_expected.to contain_exactly(offline_project_runner) + end + end + end end end diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb index e9e7fff6e6e..8d15d7eda1b 100644 --- a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb +++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe ::CachingArrayResolver do include GraphqlHelpers - include Gitlab::Graphql::Laziness let_it_be(:admins) { create_list(:user, 4, admin: true) } let(:query_context) { { current_user: admins.first } } diff --git a/spec/graphql/resolvers/group_packages_resolver_spec.rb b/spec/graphql/resolvers/group_packages_resolver_spec.rb index 48f4c8ec4ca..eba3a5f2de8 100644 --- a/spec/graphql/resolvers/group_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/group_packages_resolver_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Resolvers::GroupPackagesResolver do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } - let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:project) { create(:project, :public, group: group, path: 'a') } let(:args) do { sort: :created_desc } @@ -17,5 +17,25 @@ RSpec.describe Resolvers::GroupPackagesResolver do subject { resolve(described_class, ctx: { current_user: user }, obj: group, args: args).to_a } it_behaves_like 'group and projects packages resolver' + + describe 'project_path sorting' do + let_it_be(:project2) { create(:project, :public, group: group, path: 'b') } + let_it_be(:package) { create(:package, project: project ) } + let_it_be(:package2) { create(:package, project: project2 ) } + let_it_be(:package3) { create(:package, project: project ) } + let_it_be(:package4) { create(:package, project: project2 ) } + + context 'filter by package_name' do + let(:args) { { sort: :project_path_desc } } + + it { is_expected.to eq([package4, package2, package3, package]) } + end + + context 'filter by package_type' do + let(:args) { { sort: :project_path_asc } } + + it { is_expected.to eq([package, package3, package2, package4]) } + end + end end end diff --git a/spec/graphql/resolvers/package_details_resolver_spec.rb b/spec/graphql/resolvers/package_details_resolver_spec.rb index 1bdc069b3bb..d6acb31d4e3 100644 --- a/spec/graphql/resolvers/package_details_resolver_spec.rb +++ b/spec/graphql/resolvers/package_details_resolver_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe Resolvers::PackageDetailsResolver do include GraphqlHelpers - include ::Gitlab::Graphql::Laziness let_it_be_with_reload(:project) { create(:project) } let_it_be(:user) { project.owner } diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb index 34ddc9cd8cb..2f2aacb9ad5 100644 --- a/spec/graphql/resolvers/projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects_resolver_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Resolvers::ProjectsResolver do let_it_be(:group) { create(:group, name: 'public-group') } let_it_be(:private_group) { create(:group, name: 'private-group') } - let_it_be(:project) { create(:project, :public) } + let_it_be(:project) { create(:project, :public, topic_list: %w(ruby javascript)) } let_it_be(:other_project) { create(:project, :public) } let_it_be(:group_project) { create(:project, :public, group: group) } let_it_be(:private_project) { create(:project, :private) } @@ -70,6 +70,14 @@ RSpec.describe Resolvers::ProjectsResolver do is_expected.to be_empty end end + + context 'when topics filter is provided' do + let(:filters) { { topics: %w(ruby) } } + + it 'returns matching project' do + is_expected.to contain_exactly(project) + end + end end end @@ -138,6 +146,14 @@ RSpec.describe Resolvers::ProjectsResolver do is_expected.to match_array([named_project3, named_project1, named_project2]) end end + + context 'when topics filter is provided' do + let(:filters) { { topics: %w(ruby) } } + + it 'returns matching project' do + is_expected.to contain_exactly(project) + end + end end end end diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb index 585cd657e35..bb4938c751f 100644 --- a/spec/graphql/resolvers/timelog_resolver_spec.rb +++ b/spec/graphql/resolvers/timelog_resolver_spec.rb @@ -11,26 +11,27 @@ RSpec.describe Resolvers::TimelogResolver do context "with a group" do let_it_be(:current_user) { create(:user) } - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, :public, group: group) } - - before_all do - group.add_developer(current_user) - project.add_developer(current_user) - end - - before do - group.clear_memoization(:timelogs) - end + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :empty_repo, :public, group: group) } describe '#resolve' do + let_it_be(:short_time_ago) { 5.days.ago.beginning_of_day } + let_it_be(:medium_time_ago) { 15.days.ago.beginning_of_day } + let_it_be(:issue) { create(:issue, project: project) } - let_it_be(:issue2) { create(:issue, project: project) } - let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.beginning_of_day) } - let_it_be(:timelog2) { create(:issue_timelog, issue: issue2, spent_at: 2.days.ago.end_of_day) } - let_it_be(:timelog3) { create(:issue_timelog, issue: issue2, spent_at: 10.days.ago) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.beginning_of_day) } + let_it_be(:timelog2) { create(:issue_timelog, issue: issue, spent_at: short_time_ago.end_of_day) } + let_it_be(:timelog3) { create(:merge_request_timelog, merge_request: merge_request, spent_at: medium_time_ago) } + + let(:args) { { start_time: short_time_ago, end_time: short_time_ago.noon } } + + it 'finds all timelogs' do + timelogs = resolve_timelogs - let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } } + expect(timelogs).to contain_exactly(timelog1, timelog2, timelog3) + end it 'finds all timelogs within given dates' do timelogs = resolve_timelogs(**args) @@ -38,15 +39,28 @@ RSpec.describe Resolvers::TimelogResolver do expect(timelogs).to contain_exactly(timelog1) end - it 'return nothing when user has insufficient permissions' do - user = create(:user) - group.add_guest(current_user) + context 'when only start_date is present' do + let(:args) { { start_date: short_time_ago } } + + it 'finds timelogs until the end of day of end_date' do + timelogs = resolve_timelogs(**args) + + expect(timelogs).to contain_exactly(timelog1, timelog2) + end + end + + context 'when only end_date is present' do + let(:args) { { end_date: medium_time_ago } } + + it 'finds timelogs until the end of day of end_date' do + timelogs = resolve_timelogs(**args) - expect(resolve_timelogs(user: user, **args)).to be_empty + expect(timelogs).to contain_exactly(timelog3) + end end context 'when start_time and end_date are present' do - let(:args) { { start_time: 6.days.ago, end_date: 2.days.ago } } + let(:args) { { start_time: short_time_ago, end_date: short_time_ago } } it 'finds timelogs until the end of day of end_date' do timelogs = resolve_timelogs(**args) @@ -56,7 +70,7 @@ RSpec.describe Resolvers::TimelogResolver do end context 'when start_date and end_time are present' do - let(:args) { { start_date: 6.days.ago, end_time: 2.days.ago.noon } } + let(:args) { { start_date: short_time_ago, end_time: short_time_ago.noon } } it 'finds all timelogs within start_date and end_time' do timelogs = resolve_timelogs(**args) @@ -68,95 +82,32 @@ RSpec.describe Resolvers::TimelogResolver do context 'when arguments are invalid' do let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError } - context 'when no time or date arguments are present' do - let(:args) { {} } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Start and End arguments must be present/) - end - end - - context 'when only start_time is present' do - let(:args) { { start_time: 6.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only end_time is present' do - let(:args) { { end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only start_date is present' do - let(:args) { { start_date: 6.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when only end_date is present' do - let(:args) { { end_date: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - context 'when start_time and start_date are present' do - let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } } + let(:args) { { start_time: short_time_ago, start_date: short_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) + .to raise_error(error_class, /Provide either a start date or time, but not both/) end end context 'when end_time and end_date are present' do - let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } } + let(:args) { { end_time: short_time_ago, end_date: short_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Both Start and End arguments must be present/) - end - end - - context 'when three arguments are present' do - let(:args) { { start_date: 6.days.ago, end_date: 2.days.ago, end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /Only Time or Date arguments must be present/) + .to raise_error(error_class, /Provide either an end date or time, but not both/) end end context 'when start argument is after end argument' do - let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } } + let(:args) { { start_time: short_time_ago, end_time: medium_time_ago } } it 'returns correct error' do expect { resolve_timelogs(**args) } .to raise_error(error_class, /Start argument must be before End argument/) end end - - context 'when time range is more than 60 days' do - let(:args) { { start_time: 3.months.ago, end_time: 2.days.ago } } - - it 'returns correct error' do - expect { resolve_timelogs(**args) } - .to raise_error(error_class, /The time range period cannot contain more than 60 days/) - end - end end end end diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index dfe4a30c5b7..f27216f4d39 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -2,15 +2,17 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerType do +RSpec.describe GitlabSchema.types['CiRunner'] do specify { expect(described_class.graphql_name).to eq('CiRunner') } + specify { expect(described_class).to require_graphql_authorizations(:read_runner) } + it 'contains attributes related to a runner' do expected_fields = %w[ id description contacted_at maximum_timeout access_level active status version short_sha revision locked run_untagged ip_address runner_type tag_list ] - expect(described_class).to have_graphql_fields(*expected_fields) + expect(described_class).to include_graphql_fields(*expected_fields) end end diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb index 4df51dc8d1b..37f59770817 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe Types::GlobalIDType do + include ::Gitlab::Graphql::Laziness + include GraphqlHelpers + include GlobalIDDeprecationHelpers + let_it_be(:project) { create(:project) } let(:gid) { project.to_global_id } @@ -97,6 +101,142 @@ RSpec.describe Types::GlobalIDType do expect { type.coerce_isolated_input(invalid_gid) } .to raise_error(GraphQL::CoercionError, /does not represent an instance of Project/) end + + context 'with a deprecation' do + around(:all) do |example| + # Unset all previously memoized GlobalIDTypes to allow us to define one + # that will use the constants stubbed in the `before` block. + previous_id_types = Types::GlobalIDType.instance_variable_get(:@id_types) + Types::GlobalIDType.instance_variable_set(:@id_types, {}) + + example.run + ensure + Types::GlobalIDType.instance_variable_set(:@id_types, previous_id_types) + end + + before do + deprecation = Gitlab::GlobalId::Deprecations::Deprecation.new(old_model_name: 'OldIssue', new_model_name: 'Issue', milestone: '10.0') + + stub_global_id_deprecations(deprecation) + end + + let_it_be(:issue) { create(:issue) } + + let!(:type) { ::Types::GlobalIDType[::Issue] } + let(:deprecated_gid) { Gitlab::GlobalId.build(model_name: 'OldIssue', id: issue.id) } + let(:deprecating_gid) { Gitlab::GlobalId.build(model_name: 'Issue', id: issue.id) } + + it 'appends the description with a deprecation notice for the old Global ID' do + expect(type.to_graphql.description).to include('The older format `"gid://gitlab/OldIssue/1"` was deprecated in 10.0') + end + + describe 'coercing input against the type (parsing the Global ID string when supplied as an argument)' do + subject(:result) { type.coerce_isolated_input(gid.to_s) } + + context 'when passed the deprecated Global ID' do + let(:gid) { deprecated_gid } + + it 'changes the model_name to the new model name' do + expect(result.model_name).to eq('Issue') + end + + it 'changes the model_class to the new model class' do + expect(result.model_class).to eq(Issue) + end + + it 'can find the correct resource' do + expect(result.find).to eq(issue) + end + + it 'can find the correct resource loaded through GitlabSchema' do + expect(force(GitlabSchema.object_from_id(result, expected_class: Issue))).to eq(issue) + end + end + + context 'when passed the Global ID that is deprecating another' do + let(:gid) { deprecating_gid } + + it 'works as normal' do + expect(result).to have_attributes( + model_class: Issue, + model_name: 'Issue', + find: issue, + to_s: gid.to_s + ) + end + end + end + + describe 'coercing the result against the type (producing the Global ID string when used in a field)' do + context 'when passed the deprecated Global ID' do + let(:gid) { deprecated_gid } + + it 'works, but does not result in matching the new Global ID', :aggregate_failures do + # Note, this would normally never happen in real life as the object being parsed + # by the field would not produce the GlobalID of the deprecated model. This test + # proves that it is technically possible for the deprecated GlobalID to be + # considered parsable for the type, as opposed to raising a `GraphQL::CoercionError`. + expect(type.coerce_isolated_result(gid)).not_to eq(issue.to_global_id.to_s) + expect(type.coerce_isolated_result(gid)).to eq(gid.to_s) + end + end + + context 'when passed the Global ID that is deprecating another' do + let(:gid) { deprecating_gid } + + it 'works as normal' do + expect(type.coerce_isolated_result(gid)).to eq(issue.to_global_id.to_s) + end + end + end + + describe 'executing against the schema' do + let(:query_result) do + context = { current_user: issue.project.owner } + variables = { 'id' => gid } + + run_with_clean_state(query, context: context, variables: variables).to_h + end + + shared_examples 'a query that works with old and new GIDs' do + let(:query) do + <<-GQL + query($id: #{argument_name}!) { + issue(id: $id) { + id + } + } + GQL + end + + subject { query_result.dig('data', 'issue', 'id') } + + context 'when the argument value is the new GID' do + let(:gid) { Gitlab::GlobalId.build(model_name: 'Issue', id: issue.id) } + + it { is_expected.to be_present } + end + + context 'when the argument value is the old GID' do + let(:gid) { Gitlab::GlobalId.build(model_name: 'OldIssue', id: issue.id) } + + it { is_expected.to be_present } + end + end + + context 'when the query signature includes the old type name' do + let(:argument_name) { 'OldIssueID' } + + it_behaves_like 'a query that works with old and new GIDs' + end + + context 'when the query signature includes the new type name' do + let(:argument_name) { 'IssueID' } + + it_behaves_like 'a query that works with old and new GIDs' + end + end + end end describe 'a parameterized type with a namespace' do @@ -231,4 +371,10 @@ RSpec.describe Types::GlobalIDType do end end end + + describe '.model_name_to_graphql_name' do + it 'returns a graphql name for the given model name' do + expect(described_class.model_name_to_graphql_name('DesignManagement::Design')).to eq('DesignManagementDesignID') + end + end end diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb index 475b2a2ad34..427b5d2dcef 100644 --- a/spec/graphql/types/label_type_spec.rb +++ b/spec/graphql/types/label_type_spec.rb @@ -11,7 +11,6 @@ RSpec.describe GitlabSchema.types['Label'] do :color, :text_color, :created_at, - :remove_on_close, :updated_at ] diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index fa33b32c6c8..875a16a79e5 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['MergeRequest'] do + include GraphqlHelpers + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) } specify { expect(described_class).to require_graphql_authorizations(:read_merge_request) } @@ -19,15 +21,17 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do target_branch work_in_progress draft merge_when_pipeline_succeeds diff_head_sha merge_commit_sha user_notes_count user_discussions_count should_remove_source_branch diff_refs diff_stats diff_stats_summary - force_remove_source_branch merge_status in_progress_merge_commit_sha + force_remove_source_branch + merge_status merge_status_enum + in_progress_merge_commit_sha merge_error allow_collaboration should_be_rebased rebase_commit_sha rebase_in_progress default_merge_commit_message merge_ongoing mergeable_discussions_state web_url source_branch_exists target_branch_exists diverged_from_target_branch upvotes downvotes head_pipeline pipelines task_completion_status milestone assignees reviewers participants subscribed labels discussion_locked time_estimate - total_time_spent reference author merged_at commit_count current_user_todos - conflicts auto_merge_enabled approved_by source_branch_protected + total_time_spent human_time_estimate human_total_time_spent reference author merged_at + commit_count current_user_todos conflicts auto_merge_enabled approved_by source_branch_protected default_merge_commit_message_with_description squash_on_merge available_auto_merge_strategies has_ci mergeable commits_without_merge_commits squash security_auto_fix default_squash_commit_message auto_merge_strategy merge_user @@ -106,4 +110,27 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do execute_query end end + + describe 'merge_status_enum' do + let(:type) { GitlabSchema.types['MergeStatus'] } + + it 'has the type MergeStatus' do + expect(described_class.fields['mergeStatusEnum']).to have_graphql_type(type) + end + + let_it_be(:project) { create(:project, :public) } + + %i[preparing unchecked cannot_be_merged_recheck checking cannot_be_merged_rechecking can_be_merged cannot_be_merged].each do |state| + context "when the the DB value is #{state}" do + let(:merge_request) { create(:merge_request, :unique_branches, source_project: project, merge_status: state.to_s) } + + it 'serializes correctly' do + value = resolve_field(:merge_status_enum, merge_request) + value = type.coerce_isolated_result(value) + + expect(value).to eq(merge_request.public_merge_status.upcase) + end + end + end + end end diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb index e4144e4fa97..c1a5c93c85b 100644 --- a/spec/graphql/types/mutation_type_spec.rb +++ b/spec/graphql/types/mutation_type_spec.rb @@ -15,28 +15,6 @@ RSpec.describe Types::MutationType do expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetDraft) end - describe 'deprecated and aliased mutations' do - using RSpec::Parameterized::TableSyntax - - where(:alias_name, :canonical_name) do - 'AddAwardEmoji' | 'AwardEmojiAdd' - 'RemoveAwardEmoji' | 'AwardEmojiRemove' - 'ToggleAwardEmoji' | 'AwardEmojiToggle' - end - - with_them do - let(:alias_field) { get_field(alias_name) } - let(:canonical_field) { get_field(canonical_name) } - - it { expect(alias_field).to be_present } - it { expect(canonical_field).to be_present } - it { expect(alias_field.deprecation_reason).to be_present } - it { expect(canonical_field.deprecation_reason).not_to be_present } - it { expect(alias_field.resolver.fields).to eq(canonical_field.resolver.fields) } - it { expect(alias_field.resolver.arguments).to eq(canonical_field.resolver.arguments) } - end - end - def get_field(name) described_class.fields[GraphqlHelpers.fieldnamerize(name)] end diff --git a/spec/graphql/types/packages/package_group_sort_enum_spec.rb b/spec/graphql/types/packages/package_group_sort_enum_spec.rb new file mode 100644 index 00000000000..f2ed8f66fb3 --- /dev/null +++ b/spec/graphql/types/packages/package_group_sort_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageGroupSort'] do + it 'exposes all package group sort values' do + expect(described_class.values.keys).to contain_exactly(*%w[CREATED_DESC CREATED_ASC NAME_DESC NAME_ASC PROJECT_PATH_DESC PROJECT_PATH_ASC VERSION_DESC VERSION_ASC TYPE_DESC TYPE_ASC]) + end +end diff --git a/spec/graphql/types/packages/package_sort_enum_spec.rb b/spec/graphql/types/packages/package_sort_enum_spec.rb new file mode 100644 index 00000000000..fe9ce120c73 --- /dev/null +++ b/spec/graphql/types/packages/package_sort_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageSort'] do + it 'exposes all package sort values' do + expect(described_class.values.keys).to contain_exactly(*%w[CREATED_DESC CREATED_ASC NAME_DESC NAME_ASC VERSION_DESC VERSION_ASC TYPE_DESC TYPE_ASC]) + end +end diff --git a/spec/graphql/types/packages/pypi/metadatum_type_spec.rb b/spec/graphql/types/packages/pypi/metadatum_type_spec.rb new file mode 100644 index 00000000000..16fb3ef2098 --- /dev/null +++ b/spec/graphql/types/packages/pypi/metadatum_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PypiMetadata'] do + it 'includes pypi metadatum fields' do + expected_fields = %w[ + id required_python + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/projects/service_type_spec.rb b/spec/graphql/types/projects/service_type_spec.rb index cca7c49e132..567bdfaec24 100644 --- a/spec/graphql/types/projects/service_type_spec.rb +++ b/spec/graphql/types/projects/service_type_spec.rb @@ -9,8 +9,8 @@ RSpec.describe Types::Projects::ServiceType do it 'resolves the corresponding type for objects' do expect(described_class.resolve_type(build(:jira_service), {})).to eq(Types::Projects::Services::JiraServiceType) expect(described_class.resolve_type(build(:service), {})).to eq(Types::Projects::Services::BaseServiceType) - expect(described_class.resolve_type(build(:drone_ci_service), {})).to eq(Types::Projects::Services::BaseServiceType) - expect(described_class.resolve_type(build(:custom_issue_tracker_service), {})).to eq(Types::Projects::Services::BaseServiceType) + expect(described_class.resolve_type(build(:drone_ci_integration), {})).to eq(Types::Projects::Services::BaseServiceType) + expect(described_class.resolve_type(build(:custom_issue_tracker_integration), {})).to eq(Types::Projects::Services::BaseServiceType) end end end diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb index c23c652a378..39c2dcd07f6 100644 --- a/spec/graphql/types/projects/services_enum_spec.rb +++ b/spec/graphql/types/projects/services_enum_spec.rb @@ -3,13 +3,11 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ServiceType'] do - specify { expect(described_class.graphql_name).to eq('ServiceType') } - it 'exposes all the existing project services' do expect(described_class.values.keys).to match_array(available_services_enum) end -end -def available_services_enum - ::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase) + def available_services_enum + ::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase) + end end diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb index b87770ebe8d..f284d88180c 100644 --- a/spec/graphql/types/snippet_type_spec.rb +++ b/spec/graphql/types/snippet_type_spec.rb @@ -13,7 +13,7 @@ RSpec.describe GitlabSchema.types['Snippet'] do :visibility_level, :created_at, :updated_at, :web_url, :raw_url, :ssh_url_to_repo, :http_url_to_repo, :notes, :discussions, :user_permissions, - :description_html, :blob, :blobs] + :description_html, :blobs] expect(described_class).to have_graphql_fields(*expected_fields) end @@ -133,32 +133,6 @@ RSpec.describe GitlabSchema.types['Snippet'] do end end - describe '#blob' do - let(:query_blob) { subject.dig('data', 'snippets', 'nodes')[0]['blob'] } - - subject { GitlabSchema.execute(snippet_query_for(field: 'blob'), context: { current_user: user }).as_json } - - context 'when snippet has repository' do - let!(:snippet) { create(:personal_snippet, :repository, :public, author: user) } - let(:blob) { snippet.blobs.first } - - it 'returns the first blob from the repository' do - expect(query_blob['name']).to eq blob.name - expect(query_blob['path']).to eq blob.path - end - end - - context 'when snippet does not have a repository' do - let!(:snippet) { create(:personal_snippet, :public, author: user) } - let(:blob) { snippet.blob } - - it 'returns SnippetBlob type' do - expect(query_blob['name']).to eq blob.name - expect(query_blob['path']).to eq blob.path - end - end - end - describe '#blobs' do let_it_be(:snippet) { create(:personal_snippet, :public, author: user) } diff --git a/spec/graphql/types/snippets/blob_viewer_type_spec.rb b/spec/graphql/types/snippets/blob_viewer_type_spec.rb index 295df992c67..c3b98236f2b 100644 --- a/spec/graphql/types/snippets/blob_viewer_type_spec.rb +++ b/spec/graphql/types/snippets/blob_viewer_type_spec.rb @@ -31,7 +31,7 @@ RSpec.describe GitlabSchema.types['SnippetBlobViewer'] do end it 'returns false' do - snippet_blob = subject.dig('data', 'snippets', 'edges')[0].dig('node', 'blob') + snippet_blob = subject.dig('data', 'snippets', 'edges').first.dig('node', 'blobs', 'nodes').find { |b| b['path'] == blob.path } expect(snippet_blob['path']).to eq blob.path expect(blob_attribute).to be_nil @@ -47,10 +47,12 @@ RSpec.describe GitlabSchema.types['SnippetBlobViewer'] do snippets(ids: "#{snippet.to_global_id}") { edges { node { - blob { - path - simpleViewer { - collapsed + blobs { + nodes { + path + simpleViewer { + collapsed + } } } } @@ -73,10 +75,12 @@ RSpec.describe GitlabSchema.types['SnippetBlobViewer'] do snippets(ids: "#{snippet.to_global_id}") { edges { node { - blob { - path - simpleViewer { - tooLarge + blobs { + nodes { + path + simpleViewer { + tooLarge + } } } } diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb index 791c2fdb046..1344af89fb6 100644 --- a/spec/graphql/types/timelog_type_spec.rb +++ b/spec/graphql/types/timelog_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timelog'] do it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class).to have_graphql_fields(fields) } - it { expect(described_class).to require_graphql_authorizations(:read_group_timelogs) } + it { expect(described_class).to require_graphql_authorizations(:read_issue) } describe 'user field' do subject { described_class.fields['user'] } |