diff options
Diffstat (limited to 'spec/graphql')
46 files changed, 670 insertions, 46 deletions
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb index e5560fccf89..b06718eb16a 100644 --- a/spec/graphql/features/feature_flag_spec.rb +++ b/spec/graphql/features/feature_flag_spec.rb @@ -24,7 +24,7 @@ RSpec.describe 'Graphql Field feature flags' do let(:query_type) do query_factory do |query| - query.field :item, type, null: true, feature_flag: feature_flag, resolver: new_resolver(test_object) + query.field :item, type, null: true, _deprecated_feature_flag: feature_flag, resolver: new_resolver(test_object) end end diff --git a/spec/graphql/graphql_triggers_spec.rb b/spec/graphql/graphql_triggers_spec.rb index 84af33a5cb3..5e2ab74a0e5 100644 --- a/spec/graphql/graphql_triggers_spec.rb +++ b/spec/graphql/graphql_triggers_spec.rb @@ -47,4 +47,18 @@ RSpec.describe GraphqlTriggers do GraphqlTriggers.issuable_labels_updated(issue) end end + + describe '.issuable_dates_updated' do + it 'triggers the issuableDatesUpdated subscription' do + work_item = create(:work_item) + + expect(GitlabSchema.subscriptions).to receive(:trigger).with( + 'issuableDatesUpdated', + { issuable_id: work_item.to_gid }, + work_item + ).and_call_original + + GraphqlTriggers.issuable_dates_updated(work_item) + end + end end diff --git a/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb b/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb new file mode 100644 index 00000000000..f47f1b9869e --- /dev/null +++ b/spec/graphql/mutations/ci/runner/bulk_delete_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::Runner::BulkDelete do + include GraphqlHelpers + + let_it_be(:admin_user) { create(:user, :admin) } + let_it_be(:user) { create(:user) } + + let(:current_ctx) { { current_user: user } } + + let(:mutation_params) do + {} + end + + describe '#resolve' do + subject(:response) do + sync(resolve(described_class, args: mutation_params, ctx: current_ctx)) + end + + context 'when the user cannot admin the runner' do + let(:runner) { create(:ci_runner) } + let(:mutation_params) do + { ids: [runner.to_global_id] } + end + + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) { response } + end + end + + context 'when user can delete runners' do + let(:user) { admin_user } + let!(:runners) do + create_list(:ci_runner, 2, :instance) + end + + context 'when required arguments are missing' do + let(:mutation_params) { {} } + + context 'when admin mode is enabled', :enable_admin_mode do + it 'does not return an error' do + is_expected.to match a_hash_including(errors: []) + end + end + end + + context 'with runners specified by id' do + let(:mutation_params) do + { ids: runners.map(&:to_global_id) } + end + + context 'when admin mode is enabled', :enable_admin_mode do + it 'deletes runners', :aggregate_failures do + expect_next_instance_of( + ::Ci::Runners::BulkDeleteRunnersService, { runners: runners } + ) do |service| + expect(service).to receive(:execute).once.and_call_original + end + + expect { response }.to change { Ci::Runner.count }.by(-2) + expect(response[:errors]).to be_empty + end + + context 'when runner list is is above limit' do + before do + stub_const('::Ci::Runners::BulkDeleteRunnersService::RUNNER_LIMIT', 1) + end + + it 'only deletes up to the defined limit', :aggregate_failures do + expect { response }.to change { Ci::Runner.count } + .by(-::Ci::Runners::BulkDeleteRunnersService::RUNNER_LIMIT) + expect(response[:errors]).to be_empty + end + end + end + + context 'when admin mode is disabled', :aggregate_failures do + it 'returns error', :aggregate_failures do + expect do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + response + end + end.not_to change { Ci::Runner.count } + end + end + end + end + end +end diff --git a/spec/graphql/mutations/ci/runner/update_spec.rb b/spec/graphql/mutations/ci/runner/update_spec.rb index ffaa6e93d1b..b8efd4213fa 100644 --- a/spec/graphql/mutations/ci/runner/update_spec.rb +++ b/spec/graphql/mutations/ci/runner/update_spec.rb @@ -2,12 +2,11 @@ require 'spec_helper' -RSpec.describe 'Mutations::Ci::Runner::Update' do +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_it_be(:described_class) { Mutations::Ci::Runner::Update } let(:current_ctx) { { current_user: user } } let(:mutated_runner) { subject[:runner] } diff --git a/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb index 8296e5c6c15..102d33378c6 100644 --- a/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb +++ b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -57,17 +57,40 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Update do end context 'when there is a validation error' do - let(:occurred_at) { 'invalid date' } + context 'when note is blank' do + let(:note) { '' } - it 'does not update the timeline event' do - expect { resolve }.not_to change { timeline_event.reload.updated_at } + it 'does not update the timeline event' do + expect { resolve }.not_to change { timeline_event.reload.updated_at } + end + + it 'responds with error' do + expect(resolve).to eq(timeline_event: nil, errors: ["Note can't be blank"]) + end end - it 'responds with error' do - expect(resolve).to eq( - timeline_event: nil, - errors: ["Occurred at can't be blank"] - ) + context 'when occurred_at is blank' do + let(:occurred_at) { '' } + + it 'does not update the timeline event' do + expect { resolve }.not_to change { timeline_event.reload.updated_at } + end + + it 'responds with error' do + expect(resolve).to eq(timeline_event: nil, errors: ["Occurred at can't be blank"]) + end + end + + context 'when occurred_at is invalid' do + let(:occurred_at) { 'invalid date' } + + it 'does not update the timeline event' do + expect { resolve }.not_to change { timeline_event.reload.updated_at } + end + + it 'responds with error' do + expect(resolve).to eq(timeline_event: nil, errors: ["Occurred at can't be blank"]) + end end end end diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb index 1bb303cf99b..44bd9342b8e 100644 --- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb @@ -64,7 +64,7 @@ RSpec.describe Mutations::MergeRequests::SetLabels do end context 'when passing operation_mode as REMOVE' do - subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:remove])} + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids, operation_mode: Types::MutationOperationModeEnum.enum[:remove]) } it 'removes the labels, without removing others' do merge_request.update!(labels: [label, label2]) diff --git a/spec/graphql/mutations/merge_requests/set_reviewers_spec.rb b/spec/graphql/mutations/merge_requests/set_reviewers_spec.rb new file mode 100644 index 00000000000..df4aa885bbf --- /dev/null +++ b/spec/graphql/mutations/merge_requests/set_reviewers_spec.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::MergeRequests::SetReviewers do + let_it_be(:user) { create(:user) } + let_it_be(:merge_request, reload: true) { create(:merge_request) } + let_it_be(:reviewer) { create(:user) } + let_it_be(:reviewer2) { create(:user) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + describe '#resolve' do + let(:reviewer_usernames) { [reviewer.username] } + let(:mutated_merge_request) { subject[:merge_request] } + let(:mode) { described_class.arguments['operationMode'].default_value } + + subject do + mutation.resolve(project_path: merge_request.project.full_path, + iid: merge_request.iid, + operation_mode: mode, + reviewer_usernames: reviewer_usernames) + end + + it 'does not change reviewers if the merge_request is not accessible to the reviewers' do + merge_request.project.add_developer(user) + + expect { subject }.not_to change { merge_request.reload.reviewer_ids } + end + + it 'returns an operational error if the merge_request is not accessible to the reviewers' do + merge_request.project.add_developer(user) + + result = subject + + expect(result[:errors]).to include a_string_matching(/Cannot assign/) + end + + context 'when the user does not have permissions' do + it_behaves_like 'permission level for merge request mutation is correctly verified' + end + + context 'when the user can update the merge_request' do + before do + merge_request.project.add_developer(reviewer) + merge_request.project.add_developer(reviewer2) + merge_request.project.add_developer(user) + end + + it 'replaces the reviewer' do + merge_request.reviewers = [reviewer2] + merge_request.save! + + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request.reviewers).to contain_exactly(reviewer) + expect(subject[:errors]).to be_empty + end + + it 'returns errors when merge_request could not be updated' do + allow(merge_request).to receive(:errors_on_object).and_return(['foo']) + + expect(subject[:errors]).not_to match_array(['foo']) + end + + context 'when passing an empty reviewer list' do + let(:reviewer_usernames) { [] } + + before do + merge_request.reviewers = [reviewer] + merge_request.save! + end + + it 'removes all reviewers' do + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request.reviewers).to eq([]) + expect(subject[:errors]).to be_empty + end + end + + context 'when passing "append" as true' do + subject do + mutation.resolve( + project_path: merge_request.project.full_path, + iid: merge_request.iid, + reviewer_usernames: reviewer_usernames, + operation_mode: Types::MutationOperationModeEnum.enum[:append] + ) + end + + before do + merge_request.reviewers = [reviewer2] + merge_request.save! + + # In CE, APPEND is a NOOP as you can't have multiple reviewers + # We test multiple assignment in EE specs + stub_licensed_features(multiple_merge_request_reviewers: false) + end + + it 'is a NO-OP in FOSS' do + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request.reviewers).to contain_exactly(reviewer2) + expect(subject[:errors]).to be_empty + end + end + + context 'when passing "remove" as true' do + before do + merge_request.reviewers = [reviewer] + merge_request.save! + end + + it 'removes named reviewer' do + mutated_merge_request = mutation.resolve( + project_path: merge_request.project.full_path, + iid: merge_request.iid, + reviewer_usernames: reviewer_usernames, + operation_mode: Types::MutationOperationModeEnum.enum[:remove] + )[:merge_request] + + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request.reviewers).to eq([]) + expect(subject[:errors]).to be_empty + end + + it 'does not remove unnamed reviewer' do + mutated_merge_request = mutation.resolve( + project_path: merge_request.project.full_path, + iid: merge_request.iid, + reviewer_usernames: [reviewer2.username], + operation_mode: Types::MutationOperationModeEnum.enum[:remove] + )[:merge_request] + + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request.reviewers).to contain_exactly(reviewer) + expect(subject[:errors]).to be_empty + end + end + end + end +end diff --git a/spec/graphql/mutations/releases/create_spec.rb b/spec/graphql/mutations/releases/create_spec.rb index 1f2c3ed537f..b6b9449aa39 100644 --- a/spec/graphql/mutations/releases/create_spec.rb +++ b/spec/graphql/mutations/releases/create_spec.rb @@ -11,9 +11,9 @@ RSpec.describe Mutations::Releases::Create do let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } - let(:tag) { 'v1.1.0'} - let(:ref) { 'master'} - let(:name) { 'Version 1.0'} + let(:tag) { 'v1.1.0' } + let(:ref) { 'master' } + let(:name) { 'Version 1.0' } let(:description) { 'The first release :rocket:' } let(:released_at) { Time.parse('2018-12-10') } let(:milestones) { [milestone_12_3.title, milestone_12_4.title] } diff --git a/spec/graphql/mutations/releases/delete_spec.rb b/spec/graphql/mutations/releases/delete_spec.rb index 9934aea0031..09b420fe1ea 100644 --- a/spec/graphql/mutations/releases/delete_spec.rb +++ b/spec/graphql/mutations/releases/delete_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Mutations::Releases::Delete do let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } let_it_be(:maintainer) { create(:user) } - let_it_be(:tag) { 'v1.1.0'} + let_it_be(:tag) { 'v1.1.0' } let_it_be(:release) { create(:release, project: project, tag: tag) } let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } diff --git a/spec/graphql/mutations/releases/update_spec.rb b/spec/graphql/mutations/releases/update_spec.rb index 9fae703b85a..15b10ea0648 100644 --- a/spec/graphql/mutations/releases/update_spec.rb +++ b/spec/graphql/mutations/releases/update_spec.rb @@ -9,8 +9,8 @@ RSpec.describe Mutations::Releases::Update do let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } - let_it_be(:tag) { 'v1.1.0'} - let_it_be(:name) { 'Version 1.0'} + let_it_be(:tag) { 'v1.1.0' } + let_it_be(:name) { 'Version 1.0' } let_it_be(:description) { 'The first release :rocket:' } let_it_be(:released_at) { Time.parse('2018-12-10').utc } let_it_be(:created_at) { Time.parse('2018-11-05').utc } diff --git a/spec/graphql/resolvers/ci/runner_jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_jobs_resolver_spec.rb index 53b673e255b..ba8a127bec5 100644 --- a/spec/graphql/resolvers/ci/runner_jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runner_jobs_resolver_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Resolvers::Ci::RunnerJobsResolver do let!(:build_one) { create(:ci_build, :success, name: 'Build One', runner: runner, pipeline: pipeline) } let!(:build_two) { create(:ci_build, :success, name: 'Build Two', runner: runner, pipeline: pipeline) } let!(:build_three) { create(:ci_build, :failed, name: 'Build Three', runner: runner, pipeline: pipeline) } - let!(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline)} + let!(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline) } let(:args) { {} } let(:runner) { create(:ci_runner, :project, projects: [project]) } diff --git a/spec/graphql/resolvers/crm/contact_state_counts_resolver_spec.rb b/spec/graphql/resolvers/crm/contact_state_counts_resolver_spec.rb new file mode 100644 index 00000000000..0128ec792b3 --- /dev/null +++ b/spec/graphql/resolvers/crm/contact_state_counts_resolver_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Crm::ContactStateCountsResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :crm_enabled) } + + before_all do + create(:contact, group: group, email: "x@test.com") + create(:contact, group: group, email: "y@test.com", state: 'inactive') + create_list(:contact, 3, group: group) + create_list(:contact, 2, group: group, state: 'inactive') + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'does not raise an error and returns no counts' do + expect { resolve_counts(group) }.not_to raise_error + expect(resolve_counts(group).all).to be(0) + end + end + + context 'with authorized user' do + before do + group.add_reporter(user) + end + + context 'without parent' do + it 'returns no counts' do + expect(resolve_counts(nil).all).to be(0) + end + end + + context 'with a group' do + context 'when no filter is provided' do + it 'returns the count of all contacts' do + counts = resolve_counts(group) + expect(counts.all).to eq(7) + expect(counts.active).to eq(4) + expect(counts.inactive).to eq(3) + end + end + + context 'when search term is provided' do + it 'returns the correct counts' do + counts = resolve_counts(group, { search: "@test.com" }) + + expect(counts.all).to be(2) + expect(counts.active).to be(1) + expect(counts.inactive).to be(1) + end + end + end + end + end + + def resolve_counts(parent, args = {}, context = { current_user: user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/crm/contacts_resolver_spec.rb b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb index 98da4aeac28..c7c2d11e114 100644 --- a/spec/graphql/resolvers/crm/contacts_resolver_spec.rb +++ b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Resolvers::Crm::ContactsResolver do last_name: "DEF", email: "ghi@test.com", description: "LMNO", + organization: create(:organization, group: group), state: "inactive" ) end @@ -61,11 +62,29 @@ RSpec.describe Resolvers::Crm::ContactsResolver do end context 'when no filter is provided' do - it 'returns all the contacts in the correct order' do + it 'returns all the contacts in the default order' do expect(resolve_contacts(group)).to eq([contact_a, contact_b]) end end + context 'when a sort is provided' do + it 'returns all the contacts in the correct order' do + expect(resolve_contacts(group, { sort: 'EMAIL_DESC' })).to eq([contact_b, contact_a]) + end + end + + context 'when a sort is provided needing offset_pagination' do + it 'returns all the contacts in the correct order' do + expect(resolve_contacts(group, { sort: 'ORGANIZATION_ASC' })).to eq([contact_a, contact_b]) + end + end + + context 'when filtering for all states' do + it 'returns all the contacts in the correct order' do + expect(resolve_contacts(group, { state: 'all' })).to eq([contact_a, contact_b]) + end + end + context 'when search term is provided' do it 'returns the correct contacts' do expect(resolve_contacts(group, { search: "x@test.com" })).to contain_exactly(contact_b) diff --git a/spec/graphql/resolvers/group_members/notification_email_resolver_spec.rb b/spec/graphql/resolvers/group_members/notification_email_resolver_spec.rb index 8d0b8f9398d..e1c67bc7c18 100644 --- a/spec/graphql/resolvers/group_members/notification_email_resolver_spec.rb +++ b/spec/graphql/resolvers/group_members/notification_email_resolver_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Resolvers::GroupMembers::NotificationEmailResolver do expect(described_class).to have_nullable_graphql_type(GraphQL::Types::String) end - subject { batch_sync { resolve_notification_email(developer.group_members.first, current_user) }} + subject { batch_sync { resolve_notification_email(developer.group_members.first, current_user) } } context 'when current_user is admin' do let(:current_user) { create(:user, :admin) } diff --git a/spec/graphql/resolvers/project_jobs_resolver_spec.rb b/spec/graphql/resolvers/project_jobs_resolver_spec.rb index bb711a4c857..eb9d31ea7e5 100644 --- a/spec/graphql/resolvers/project_jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/project_jobs_resolver_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Resolvers::ProjectJobsResolver do let_it_be(:failed_build) { create(:ci_build, :failed, name: 'Build Three', pipeline: pipeline) } let_it_be(:pending_build) { create(:ci_build, :pending, name: 'Build Three', pipeline: pipeline) } - let(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline)} + let(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline) } let(:args) { {} } let(:current_user) { create(:user) } diff --git a/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb b/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb new file mode 100644 index 00000000000..ef1b18f0a11 --- /dev/null +++ b/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Projects::ForkTargetsResolver do + include GraphqlHelpers + + let_it_be(:group) { create(:group, path: 'namespace-group') } + let_it_be(:another_group) { create(:group, path: 'namespace-another-group') } + let_it_be(:project) { create(:project, :private, group: group) } + let_it_be(:user) { create(:user, username: 'namespace-user', maintainer_projects: [project]) } + + let(:args) { { search: 'namespace' } } + + describe '#resolve' do + before_all do + group.add_owner(user) + another_group.add_owner(user) + end + + it 'returns forkable namespaces' do + expect_next_instance_of(ForkTargetsFinder) do |finder| + expect(finder).to receive(:execute).with(args).and_call_original + end + + expect(resolve_targets(args).items).to match_array([user.namespace, project.namespace, another_group]) + end + end + + context 'when a user cannot fork the project' do + let(:user) { create(:user) } + + it 'does not return results' do + project.add_guest(user) + + expect(resolve_targets(args)).to be_nil + end + end + + def resolve_targets(args, opts = {}) + field_options = described_class.field_options.merge( + owner: resolver_parent, + name: 'field_value' + ).merge(opts) + + field = ::Types::BaseField.new(**field_options) + resolve_field(field, project, args: args, ctx: { current_user: user }, object_type: resolver_parent) + end +end diff --git a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb index 854e763fbdd..546b8592546 100644 --- a/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/grafana_integration_resolver_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Resolvers::Projects::GrafanaIntegrationResolver do let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } - let_it_be(:grafana_integration) { create(:grafana_integration, project: project)} + let_it_be(:grafana_integration) { create(:grafana_integration, project: project) } describe '#resolve' do context 'when object is not a project' do @@ -19,7 +19,7 @@ RSpec.describe Resolvers::Projects::GrafanaIntegrationResolver do end context 'when object is nil' do - it { expect(resolve_integration(obj: nil)).to eq nil} + it { expect(resolve_integration(obj: nil)).to eq nil } end end diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb index 2685115d1a2..453fafb9590 100644 --- a/spec/graphql/resolvers/projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects_resolver_spec.rb @@ -142,7 +142,7 @@ RSpec.describe Resolvers::ProjectsResolver do context 'when no sort is provided' do it 'returns projects in descending order by id' do - is_expected.to match_array((visible_projecs + named_projects).sort_by { |p| p[:id]}.reverse ) + is_expected.to match_array((visible_projecs + named_projects).sort_by { |p| p[:id] }.reverse ) end end end diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index 439678e7e16..b85716e4d21 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -209,7 +209,7 @@ RSpec.describe Types::BaseField do describe '#visible?' do context 'and has a feature_flag' do let(:flag) { :test_feature } - let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, feature_flag: flag, null: false) } + let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, _deprecated_feature_flag: flag, null: false) } let(:context) { {} } before do @@ -253,7 +253,7 @@ RSpec.describe Types::BaseField do describe '#description' do context 'feature flag given' do - let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, feature_flag: flag, null: false, description: 'Test description.') } + let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, _deprecated_feature_flag: flag, null: false, description: 'Test description.') } let(:flag) { :test_flag } it 'prepends the description' do @@ -299,7 +299,7 @@ RSpec.describe Types::BaseField do end it 'returns the correct availability in the description' do - expect(field.description). to eq expected_description + expect(field.description).to eq expected_description end end end @@ -313,11 +313,11 @@ RSpec.describe Types::BaseField do described_class.new(**base_args.merge(args)) end - it 'interacts well with the `feature_flag` property' do + it 'interacts well with the `_deprecated_feature_flag` property' do field = subject( deprecated: { milestone: '1.10', reason: 'Deprecation reason' }, description: 'Field description.', - feature_flag: 'foo_flag' + _deprecated_feature_flag: 'foo_flag' ) expect(field.description).to start_with('Field description. Available only when feature flag `foo_flag` is enabled.') diff --git a/spec/graphql/types/ci/group_variable_type_spec.rb b/spec/graphql/types/ci/group_variable_type_spec.rb new file mode 100644 index 00000000000..106935642f2 --- /dev/null +++ b/spec/graphql/types/ci/group_variable_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiGroupVariable'] do + specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } + + specify { expect(described_class).to have_graphql_fields(:environment_scope, :masked, :protected).at_least } +end diff --git a/spec/graphql/types/ci/instance_variable_type_spec.rb b/spec/graphql/types/ci/instance_variable_type_spec.rb new file mode 100644 index 00000000000..cf4aaed31f1 --- /dev/null +++ b/spec/graphql/types/ci/instance_variable_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiInstanceVariable'] do + specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } + + specify { expect(described_class).to have_graphql_fields(:masked, :protected).at_least } +end diff --git a/spec/graphql/types/ci/job_token_scope_type_spec.rb b/spec/graphql/types/ci/job_token_scope_type_spec.rb index c1a3c4dd54d..457d46b6896 100644 --- a/spec/graphql/types/ci/job_token_scope_type_spec.rb +++ b/spec/graphql/types/ci/job_token_scope_type_spec.rb @@ -34,7 +34,7 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json } let(:projects_field) { subject.dig('data', 'project', 'ciJobTokenScope', 'projects', 'nodes') } - let(:returned_project_paths) { projects_field.map { |project| project['path']} } + let(:returned_project_paths) { projects_field.map { |project| project['path'] } } context 'with access to scope' do before do diff --git a/spec/graphql/types/ci/manual_variable_type_spec.rb b/spec/graphql/types/ci/manual_variable_type_spec.rb new file mode 100644 index 00000000000..2884c818a52 --- /dev/null +++ b/spec/graphql/types/ci/manual_variable_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiManualVariable'] do + specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } +end diff --git a/spec/graphql/types/ci/project_variable_type_spec.rb b/spec/graphql/types/ci/project_variable_type_spec.rb new file mode 100644 index 00000000000..e6e045b2bca --- /dev/null +++ b/spec/graphql/types/ci/project_variable_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiProjectVariable'] do + specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } + + specify { expect(described_class).to have_graphql_fields(:environment_scope, :masked, :protected).at_least } +end diff --git a/spec/graphql/types/ci/runner_upgrade_status_type_enum_spec.rb b/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb index 03c784dcbe7..ef378f3fc5a 100644 --- a/spec/graphql/types/ci/runner_upgrade_status_type_enum_spec.rb +++ b/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerUpgradeStatusTypeEnum do +RSpec.describe Types::Ci::RunnerUpgradeStatusEnum do let(:model_only_enum_values) { %w[not_processed] } let(:expected_graphql_source_values) do Ci::RunnerVersion.statuses.keys - model_only_enum_values end - specify { expect(described_class.graphql_name).to eq('CiRunnerUpgradeStatusType') } + specify { expect(described_class.graphql_name).to eq('CiRunnerUpgradeStatus') } it 'exposes all upgrade status values except not_processed' do expect(described_class.values.keys).to match_array( diff --git a/spec/graphql/types/ci/variable_input_type_spec.rb b/spec/graphql/types/ci/variable_input_type_spec.rb new file mode 100644 index 00000000000..a56b6287dee --- /dev/null +++ b/spec/graphql/types/ci/variable_input_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiVariableInput'] do + include GraphqlHelpers + + it 'has the correct arguments' do + expect(described_class.arguments.keys).to match_array(%w[key value]) + end +end diff --git a/spec/graphql/types/ci/variable_type_spec.rb b/spec/graphql/types/ci/variable_interface_spec.rb index a81e6adbab6..8cef0ac2a14 100644 --- a/spec/graphql/types/ci/variable_type_spec.rb +++ b/spec/graphql/types/ci/variable_interface_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiVariable'] do - it 'contains attributes related to CI variables' do + specify do expect(described_class).to have_graphql_fields( - :id, :key, :value, :variable_type, :protected, :masked, :raw, :environment_scope - ) + :id, :key, :value, :variable_type, :raw + ).at_least end end diff --git a/spec/graphql/types/customer_relations/contact_sort_enum_spec.rb b/spec/graphql/types/customer_relations/contact_sort_enum_spec.rb new file mode 100644 index 00000000000..5b0538042c8 --- /dev/null +++ b/spec/graphql/types/customer_relations/contact_sort_enum_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContactSort'] do + specify { expect(described_class.graphql_name).to eq('ContactSort') } + + it_behaves_like 'common sort values' + + it 'exposes all the contact sort values' do + expect(described_class.values.keys).to include( + *%w[ + FIRST_NAME_ASC + FIRST_NAME_DESC + LAST_NAME_ASC + LAST_NAME_DESC + EMAIL_ASC + EMAIL_DESC + PHONE_ASC + PHONE_DESC + DESCRIPTION_ASC + DESCRIPTION_DESC + ORGANIZATION_ASC + ORGANIZATION_DESC + ] + ) + end +end diff --git a/spec/graphql/types/customer_relations/contact_state_counts_type_spec.rb b/spec/graphql/types/customer_relations/contact_state_counts_type_spec.rb new file mode 100644 index 00000000000..b022febb90f --- /dev/null +++ b/spec/graphql/types/customer_relations/contact_state_counts_type_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContactStateCounts'] do + let(:fields) do + %w[ + all + active + inactive + ] + end + + it { expect(described_class.graphql_name).to eq('ContactStateCounts') } + it { expect(described_class).to have_graphql_fields(fields) } + it { expect(described_class).to require_graphql_authorizations(:read_crm_contact) } +end diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb index a57db9234f1..fa0b34113bc 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -114,7 +114,11 @@ RSpec.describe Types::GlobalIDType do end before do - deprecation = Gitlab::GlobalId::Deprecations::Deprecation.new(old_model_name: 'OldIssue', new_model_name: 'Issue', milestone: '10.0') + deprecation = Gitlab::GlobalId::Deprecations::NameDeprecation.new( + old_name: 'OldIssue', + new_name: 'Issue', + milestone: '10.0' + ) stub_global_id_deprecations(deprecation) end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index 69c7eaf111f..72b3bb90194 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Group'] do + include GraphqlHelpers + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Group) } specify { expect(described_class.graphql_name).to eq('Group') } @@ -22,8 +24,8 @@ RSpec.describe GitlabSchema.types['Group'] do dependency_proxy_blobs dependency_proxy_image_count dependency_proxy_blob_count dependency_proxy_total_size dependency_proxy_image_prefix dependency_proxy_image_ttl_policy - shared_runners_setting timelogs organizations contacts work_item_types - recent_issue_boards ci_variables + shared_runners_setting timelogs organizations contacts contact_state_counts + work_item_types recent_issue_boards ci_variables ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -53,7 +55,52 @@ RSpec.describe GitlabSchema.types['Group'] do end end + describe 'contact_state_counts field' do + subject { described_class.fields['contactStateCounts'] } + + it { is_expected.to have_graphql_type(Types::CustomerRelations::ContactStateCountsType) } + it { is_expected.to have_graphql_resolver(Resolvers::Crm::ContactStateCountsResolver) } + end + it_behaves_like 'a GraphQL type with labels' do let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] } end + + describe 'milestones' do + let(:user) { create(:user) } + let(:subgroup) { create(:group, parent: create(:group)) } + let(:query) do + %( + query { + group(fullPath: "#{subgroup.full_path}") { + milestones { + nodes { + id + title + projectMilestone + groupMilestone + subgroupMilestone + } + } + } + } + ) + end + + def clean_state_query + run_with_clean_state(query, context: { current_user: user }) + end + + it 'avoids N+1 queries' do + subgroup.add_reporter(user) + + create(:milestone, group: subgroup) + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query } + + create_list(:milestone, 2, group: subgroup) + + expect { clean_state_query }.not_to exceed_all_query_limit(control) + end + end end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index e7454b85357..2a0ae79b2c4 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -167,7 +167,7 @@ RSpec.describe GitlabSchema.types['Issue'] do shared_examples_for 'does not include private notes' do it "does not return private notes" do notes = subject.dig("data", "project", "issue", "notes", 'edges') - notes_body = notes.map {|n| n.dig('node', 'body')} + notes_body = notes.map { |n| n.dig('node', 'body') } expect(notes.size).to eq 1 expect(notes_body).not_to include(private_note_body) @@ -178,7 +178,7 @@ RSpec.describe GitlabSchema.types['Issue'] do shared_examples_for 'includes private notes' do it "returns all notes" do notes = subject.dig("data", "project", "issue", "notes", 'edges') - notes_body = notes.map {|n| n.dig('node', 'body')} + notes_body = notes.map { |n| n.dig('node', 'body') } expect(notes.size).to eq 2 expect(notes_body).to include(private_note_body) @@ -209,7 +209,7 @@ RSpec.describe GitlabSchema.types['Issue'] do end describe 'hidden', :enable_admin_mode do - let_it_be(:admin) { create(:user, :admin)} + let_it_be(:admin) { create(:user, :admin) } let_it_be(:banned_user) { create(:user, :banned) } let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } diff --git a/spec/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb index 3b7f7e65e4b..168a6ba4eaa 100644 --- a/spec/graphql/types/namespace_type_spec.rb +++ b/spec/graphql/types/namespace_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Namespace'] do expected_fields = %w[ id name path full_name full_path description description_html visibility lfs_enabled request_access_enabled projects root_storage_statistics shared_runners_setting + timelog_categories ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb index 03ff7828cf5..cbf7f086dbe 100644 --- a/spec/graphql/types/notes/note_type_spec.rb +++ b/spec/graphql/types/notes/note_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Note'] do body body_html confidential + internal created_at discussion id diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index ed93d31da0f..5ff7653ce39 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -37,7 +37,7 @@ RSpec.describe GitlabSchema.types['Project'] do cluster_agent cluster_agents agent_configurations ci_template timelogs merge_commit_template squash_commit_template work_item_types recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables - recent_issue_boards ci_config_path_or_default ci_variables + timelog_categories fork_targets ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -195,8 +195,8 @@ RSpec.describe GitlabSchema.types['Project'] do expect(secure_analyzers['type']).to eq('string') expect(secure_analyzers['field']).to eq('SECURE_ANALYZERS_PREFIX') expect(secure_analyzers['label']).to eq('Image prefix') - expect(secure_analyzers['defaultValue']).to eq(secure_analyzers_prefix) - expect(secure_analyzers['value']).to eq(secure_analyzers_prefix) + expect(secure_analyzers['defaultValue']).to eq('$CI_TEMPLATE_REGISTRY_HOST/security-products') + expect(secure_analyzers['value']).to eq('$CI_TEMPLATE_REGISTRY_HOST/security-products') expect(secure_analyzers['size']).to eq('LARGE') expect(secure_analyzers['options']).to be_nil end diff --git a/spec/graphql/types/projects/service_type_enum_spec.rb b/spec/graphql/types/projects/service_type_enum_spec.rb index ead69e60f6c..f7256910bb0 100644 --- a/spec/graphql/types/projects/service_type_enum_spec.rb +++ b/spec/graphql/types/projects/service_type_enum_spec.rb @@ -35,6 +35,7 @@ RSpec.describe GitlabSchema.types['ServiceType'] do PIPELINES_EMAIL_SERVICE PIVOTALTRACKER_SERVICE PROMETHEUS_SERVICE + PUMBLE_SERVICE PUSHOVER_SERVICE REDMINE_SERVICE SHIMO_SERVICE diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb index 1a2629ed422..9b043fa52cf 100644 --- a/spec/graphql/types/subscription_type_spec.rb +++ b/spec/graphql/types/subscription_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Subscription'] do issue_crm_contacts_updated issuable_title_updated issuable_labels_updated + issuable_dates_updated ] expect(described_class).to have_graphql_fields(*expected_fields).only diff --git a/spec/graphql/types/time_tracking/timelog_category_type_spec.rb b/spec/graphql/types/time_tracking/timelog_category_type_spec.rb new file mode 100644 index 00000000000..a14069e8b58 --- /dev/null +++ b/spec/graphql/types/time_tracking/timelog_category_type_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['TimeTrackingTimelogCategory'] do + let(:fields) do + %w[ + id + name + description + color + billable + billing_rate + created_at + updated_at + ] + end + + it { expect(described_class.graphql_name).to eq('TimeTrackingTimelogCategory') } + it { expect(described_class).to have_graphql_fields(fields) } + it { expect(described_class).to require_graphql_authorizations(:read_timelog_category) } +end diff --git a/spec/graphql/types/upload_type_spec.rb b/spec/graphql/types/upload_type_spec.rb new file mode 100644 index 00000000000..2b959fbf105 --- /dev/null +++ b/spec/graphql/types/upload_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['FileUpload'] do + it { expect(described_class).to require_graphql_authorizations(:read_upload) } + + it 'has the expected fields' do + expected_fields = %w[id size path] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index fec6a771640..dcf25ff0667 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -49,7 +49,7 @@ RSpec.describe GitlabSchema.types['User'] do end describe 'name field' do - let_it_be(:admin) { create(:user, :admin)} + let_it_be(:admin) { create(:user, :admin) } let_it_be(:user) { create(:user) } let_it_be(:requested_user) { create(:user, name: 'John Smith') } let_it_be(:requested_project_bot) { create(:user, :project_bot, name: 'Project bot') } diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb index 7ed58786b5b..c556424b0b4 100644 --- a/spec/graphql/types/work_item_type_spec.rb +++ b/spec/graphql/types/work_item_type_spec.rb @@ -11,16 +11,21 @@ RSpec.describe GitlabSchema.types['WorkItem'] do it 'has specific fields' do fields = %i[ + confidential description description_html id iid lock_version + project state title title_html userPermissions widgets work_item_type + created_at + updated_at + closed_at ] fields.each do |field_name| diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb index caf986c961f..b9e8edacf15 100644 --- a/spec/graphql/types/work_items/widget_interface_spec.rb +++ b/spec/graphql/types/work_items/widget_interface_spec.rb @@ -18,6 +18,7 @@ RSpec.describe Types::WorkItems::WidgetInterface do WorkItems::Widgets::Description | Types::WorkItems::Widgets::DescriptionType WorkItems::Widgets::Hierarchy | Types::WorkItems::Widgets::HierarchyType WorkItems::Widgets::Assignees | Types::WorkItems::Widgets::AssigneesType + WorkItems::Widgets::Labels | Types::WorkItems::Widgets::LabelsType end with_them do diff --git a/spec/graphql/types/work_items/widgets/assignees_input_type_spec.rb b/spec/graphql/types/work_items/widgets/assignees_input_type_spec.rb new file mode 100644 index 00000000000..2fcda2a43be --- /dev/null +++ b/spec/graphql/types/work_items/widgets/assignees_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::WorkItems::Widgets::AssigneesInputType do + it { expect(described_class.graphql_name).to eq('WorkItemWidgetAssigneesInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[assigneeIds]) } +end diff --git a/spec/graphql/types/work_items/widgets/labels_type_spec.rb b/spec/graphql/types/work_items/widgets/labels_type_spec.rb new file mode 100644 index 00000000000..028ebe979f3 --- /dev/null +++ b/spec/graphql/types/work_items/widgets/labels_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::LabelsType do + it 'exposes the expected fields' do + expected_fields = %i[labels allowsScopedLabels type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb b/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb new file mode 100644 index 00000000000..ddc26d964be --- /dev/null +++ b/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::StartAndDueDateType do + it 'exposes the expected fields' do + expected_fields = %i[due_date start_date type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb b/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb new file mode 100644 index 00000000000..91631093e4e --- /dev/null +++ b/spec/graphql/types/work_items/widgets/start_and_due_date_update_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::WorkItems::Widgets::StartAndDueDateUpdateInputType do + it { expect(described_class.graphql_name).to eq('WorkItemWidgetStartAndDueDateUpdateInput') } + + it { expect(described_class.arguments.keys).to contain_exactly('startDate', 'dueDate') } +end |