diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-20 23:50:22 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-20 23:50:22 +0000 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/graphql/types | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff) | |
download | gitlab-ce-9dc93a4519d9d5d7be48ff274127136236a3adb3.tar.gz |
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'spec/graphql/types')
35 files changed, 966 insertions, 40 deletions
diff --git a/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb index c50092d7f0e..d1c2b4044c1 100644 --- a/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb +++ b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb @@ -11,6 +11,7 @@ RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do describe 'authorization' do let_it_be(:measurement) { create(:usage_trends_measurement, :project_count) } + let(:user) { create(:user) } let(:query) do @@ -44,7 +45,7 @@ RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do let(:user) { create(:user, :admin) } before do - stub_feature_flags(user_mode_in_session: false) + stub_application_setting(admin_mode: false) end it 'returns data' do diff --git a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb index b10c2a2ab2a..d057afb331c 100644 --- a/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb +++ b/spec/graphql/types/alert_management/prometheus_integration_type_spec.rb @@ -48,15 +48,21 @@ RSpec.describe GitlabSchema.types['AlertManagementPrometheusIntegration'] do end end - context 'without project' do - let_it_be(:integration) { create(:prometheus_service, project: nil, group: create(:group)) } - - it_behaves_like 'has field with value', 'token' do - let(:value) { nil } - end - - it_behaves_like 'has field with value', 'url' do - let(:value) { nil } + describe 'a group integration' do + let_it_be(:group) { create(:group) } + let_it_be(:integration) { create(:prometheus_service, project: nil, group: group) } + + # Since it is impossible to authorize the parent here, given that the + # project is nil, all fields should be redacted: + + described_class.fields.each_key do |field_name| + context "field: #{field_name}" do + it 'is redacted' do + expect do + resolve_field(field_name, integration, current_user: user) + end.to raise_error(GraphqlHelpers::UnauthorizedObject) + end + end end end end diff --git a/spec/graphql/types/base_enum_spec.rb b/spec/graphql/types/base_enum_spec.rb index 744aee40044..bab0278ee25 100644 --- a/spec/graphql/types/base_enum_spec.rb +++ b/spec/graphql/types/base_enum_spec.rb @@ -3,6 +3,38 @@ require 'spec_helper' RSpec.describe Types::BaseEnum do + describe '.from_rails_enum' do + let(:enum_type) { Class.new(described_class) } + let(:template) { "The name is '%{name}', James %{name}." } + + let(:enum) do + { + 'foo' => 1, + 'bar' => 2, + 'baz' => 100 + } + end + + it 'contructs the correct values' do + enum_type.from_rails_enum(enum, description: template) + + expect(enum_type.values).to match( + 'FOO' => have_attributes( + description: "The name is 'foo', James foo.", + value: 'foo' + ), + 'BAR' => have_attributes( + description: "The name is 'bar', James bar.", + value: 'bar' + ), + 'BAZ' => have_attributes( + description: "The name is 'baz', James baz.", + value: 'baz' + ) + ) + end + end + describe '.declarative_enum' do let(:use_name) { true } let(:use_description) { true } @@ -26,12 +58,15 @@ RSpec.describe Types::BaseEnum do end end - subject(:set_declarative_enum) { enum_type.declarative_enum(enum_module, use_name: use_name, use_description: use_description) } + subject(:set_declarative_enum) do + enum_type.declarative_enum(enum_module, use_name: use_name, use_description: use_description) + end describe '#graphql_name' do context 'when the use_name is `true`' do it 'changes the graphql_name' do - expect { set_declarative_enum }.to change { enum_type.graphql_name }.from('OriginalName').to('Name') + expect { set_declarative_enum } + .to change(enum_type, :graphql_name).from('OriginalName').to('Name') end end @@ -39,7 +74,8 @@ RSpec.describe Types::BaseEnum do let(:use_name) { false } it 'does not change the graphql_name' do - expect { set_declarative_enum }.not_to change { enum_type.graphql_name }.from('OriginalName') + expect { set_declarative_enum } + .not_to change(enum_type, :graphql_name).from('OriginalName') end end end @@ -47,7 +83,8 @@ RSpec.describe Types::BaseEnum do describe '#description' do context 'when the use_description is `true`' do it 'changes the description' do - expect { set_declarative_enum }.to change { enum_type.description }.from('Original description').to('Description') + expect { set_declarative_enum } + .to change(enum_type, :description).from('Original description').to('Description') end end @@ -55,7 +92,8 @@ RSpec.describe Types::BaseEnum do let(:use_description) { false } it 'does not change the description' do - expect { set_declarative_enum }.not_to change { enum_type.description }.from('Original description') + expect { set_declarative_enum } + .not_to change(enum_type, :description).from('Original description') end end end diff --git a/spec/graphql/types/base_object_spec.rb b/spec/graphql/types/base_object_spec.rb new file mode 100644 index 00000000000..d8f2ef58ea5 --- /dev/null +++ b/spec/graphql/types/base_object_spec.rb @@ -0,0 +1,432 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::BaseObject do + include GraphqlHelpers + + describe 'scoping items' do + let_it_be(:custom_auth) do + Class.new(::Gitlab::Graphql::Authorize::ObjectAuthorization) do + def any? + true + end + + def ok?(object, _current_user) + return false if object == { id: 100 } + return false if object.try(:deactivated?) + + true + end + end + end + + let_it_be(:test_schema) do + auth = custom_auth.new(nil) + + base_object = Class.new(described_class) do + # Override authorization so we don't need to mock Ability + define_singleton_method :authorization do + auth + end + end + + y_type = Class.new(base_object) do + graphql_name 'Y' + authorize :read_y + field :id, Integer, null: false + + def id + object[:id] + end + end + + number_type = Module.new do + include ::Types::BaseInterface + + graphql_name 'Number' + + field :value, Integer, null: false + end + + odd_type = Class.new(described_class) do + graphql_name 'Odd' + implements number_type + + authorize :read_odd + field :odd_value, Integer, null: false + + def odd_value + object[:value] + end + end + + even_type = Class.new(described_class) do + graphql_name 'Even' + implements number_type + + authorize :read_even + field :even_value, Integer, null: false + + def even_value + object[:value] + end + end + + # an abstract type, delegating authorization to members + odd_or_even = Class.new(::Types::BaseUnion) do + graphql_name 'OddOrEven' + + possible_types odd_type, even_type + + define_singleton_method :resolve_type do |object, ctx| + if object[:value].odd? + odd_type + else + even_type + end + end + end + + number_type.define_singleton_method :resolve_type do |object, ctx| + odd_or_even.resolve_type(object, ctx) + end + + x_type = Class.new(base_object) do + graphql_name 'X' + # Scalar types + field :title, String, null: true + # monomorphic types + field :lazy_list_of_ys, [y_type], null: true + field :list_of_lazy_ys, [y_type], null: true + field :array_ys_conn, y_type.connection_type, null: true + # polymorphic types + field :polymorphic_conn, odd_or_even.connection_type, null: true + field :polymorphic_object, odd_or_even, null: true do + argument :value, Integer, required: true + end + field :interface_conn, number_type.connection_type, null: true + + def lazy_list_of_ys + ::Gitlab::Graphql::Lazy.new { object[:ys] } + end + + def list_of_lazy_ys + object[:ys].map { |y| ::Gitlab::Graphql::Lazy.new { y } } + end + + def array_ys_conn + object[:ys].dup + end + + def polymorphic_conn + object[:values].dup + end + alias_method :interface_conn, :polymorphic_conn + + def polymorphic_object(value) + value + end + end + + user_type = Class.new(base_object) do + graphql_name 'User' + authorize :read_user + field 'name', String, null: true + end + + Class.new(GraphQL::Schema) do + lazy_resolve ::Gitlab::Graphql::Lazy, :force + use ::GraphQL::Pagination::Connections + use ::Gitlab::Graphql::Pagination::Connections + + query(Class.new(::Types::BaseObject) do + graphql_name 'Query' + field :x, x_type, null: true + field :users, user_type.connection_type, null: true + + def x + ::Gitlab::Graphql::Lazy.new { context[:x] } + end + + def users + ::Gitlab::Graphql::Lazy.new { User.id_in(context[:user_ids]).order(id: :asc) } + end + end) + + def unauthorized_object(err) + nil + end + end + end + + def document(path) + GraphQL.parse(<<~GQL) + query { + x { + title + #{query_graphql_path(path, 'id')} + } + } + GQL + end + + let(:data) do + { + x: { + title: 'Hey', + ys: [{ id: 1 }, { id: 100 }, { id: 2 }] + } + } + end + + shared_examples 'array member redaction' do |path| + let(:result) do + query = GraphQL::Query.new(test_schema, document: document(path), context: data) + query.result.to_h + end + + it 'redacts the unauthorized array member' do + expect(graphql_dig_at(result, 'data', 'x', 'title')).to eq('Hey') + expect(graphql_dig_at(result, 'data', 'x', *path)).to contain_exactly( + eq({ 'id' => 1 }), + eq({ 'id' => 2 }) + ) + end + end + + # For example a batchloaded association + describe 'a lazy list' do + it_behaves_like 'array member redaction', %w[lazyListOfYs] + end + + # For example using a batchloader to map over a set of IDs + describe 'a list of lazy items' do + it_behaves_like 'array member redaction', %w[listOfLazyYs] + end + + describe 'an array connection of items' do + it_behaves_like 'array member redaction', %w[arrayYsConn nodes] + end + + describe 'an array connection of items, selecting edges' do + it_behaves_like 'array member redaction', %w[arrayYsConn edges node] + end + + it 'paginates arrays correctly' do + n = 7 + + data = { + x: { + ys: (95..105).to_a.map { |id| { id: id } } + } + } + + doc = lambda do |after| + GraphQL.parse(<<~GQL) + query { + x { + ys: arrayYsConn(#{attributes_to_graphql(first: n, after: after)}) { + pageInfo { + hasNextPage + hasPreviousPage + endCursor + } + nodes { id } + } + } + } + GQL + end + returned_items = ->(ids) { ids.to_a.map { |id| eq({ 'id' => id }) } } + + query = GraphQL::Query.new(test_schema, document: doc[nil], context: data) + result = query.result.to_h + + ys = result.dig('data', 'x', 'ys', 'nodes') + page = result.dig('data', 'x', 'ys', 'pageInfo') + # We expect this page to be smaller, since we paginate before redaction + expect(ys).to match_array(returned_items[(95..101).to_a - [100]]) + expect(page).to include('hasNextPage' => true, 'hasPreviousPage' => false) + + cursor = page['endCursor'] + query_2 = GraphQL::Query.new(test_schema, document: doc[cursor], context: data) + result_2 = query_2.result.to_h + + ys = result_2.dig('data', 'x', 'ys', 'nodes') + page = result_2.dig('data', 'x', 'ys', 'pageInfo') + expect(ys).to match_array(returned_items[102..105]) + expect(page).to include('hasNextPage' => false, 'hasPreviousPage' => true) + end + + it 'filters connections correctly' do + active_users = create_list(:user, 3, state: :active) + inactive = create(:user, state: :deactivated) + + data = { user_ids: [inactive, *active_users].map(&:id) } + + doc = GraphQL.parse(<<~GQL) + query { + users { nodes { name } } + } + GQL + + query = GraphQL::Query.new(test_schema, document: doc, context: data) + result = query.result.to_h + + expect(result.dig('data', 'users', 'nodes')).to match_array(active_users.map do |u| + eq({ 'name' => u.name }) + end) + end + + it 'filters polymorphic connections' do + data = { + current_user: :the_user, + x: { + values: [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }] + } + } + + doc = GraphQL.parse(<<~GQL) + query { + x { + things: polymorphicConn { + nodes { + ... on Odd { oddValue } + ... on Even { evenValue } + } + } + } + } + GQL + + # Each ability check happens twice: once in the collection, and once + # on the type. We expect the ability checks to be cached. + expect(Ability).to receive(:allowed?).twice + .with(:the_user, :read_odd, { value: 1 }).and_return(true) + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_odd, { value: 3 }).and_return(false) + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_even, { value: 2 }).and_return(false) + expect(Ability).to receive(:allowed?).twice + .with(:the_user, :read_even, { value: 4 }).and_return(true) + + query = GraphQL::Query.new(test_schema, document: doc, context: data) + result = query.result.to_h + + things = result.dig('data', 'x', 'things', 'nodes') + + expect(things).to contain_exactly( + { 'oddValue' => 1 }, + { 'evenValue' => 4 } + ) + end + + it 'filters interface connections' do + data = { + current_user: :the_user, + x: { + values: [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }] + } + } + + doc = GraphQL.parse(<<~GQL) + query { + x { + things: interfaceConn { + nodes { + value + ... on Odd { oddValue } + ... on Even { evenValue } + } + } + } + } + GQL + + # Each ability check happens twice: once in the collection, and once + # on the type. We expect the ability checks to be cached. + expect(Ability).to receive(:allowed?).twice + .with(:the_user, :read_odd, { value: 1 }).and_return(true) + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_odd, { value: 3 }).and_return(false) + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_even, { value: 2 }).and_return(false) + expect(Ability).to receive(:allowed?).twice + .with(:the_user, :read_even, { value: 4 }).and_return(true) + + query = GraphQL::Query.new(test_schema, document: doc, context: data) + result = query.result.to_h + + things = result.dig('data', 'x', 'things', 'nodes') + + expect(things).to contain_exactly( + { 'value' => 1, 'oddValue' => 1 }, + { 'value' => 4, 'evenValue' => 4 } + ) + end + + it 'redacts polymorphic objects' do + data = { + current_user: :the_user, + x: { + values: [{ value: 1 }] + } + } + + doc = GraphQL.parse(<<~GQL) + query { + x { + ok: polymorphicObject(value: 1) { + ... on Odd { oddValue } + ... on Even { evenValue } + } + bad: polymorphicObject(value: 3) { + ... on Odd { oddValue } + ... on Even { evenValue } + } + } + } + GQL + + # Each ability check happens twice: once in the collection, and once + # on the type. We expect the ability checks to be cached. + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_odd, { value: 1 }).and_return(true) + expect(Ability).to receive(:allowed?).once + .with(:the_user, :read_odd, { value: 3 }).and_return(false) + + query = GraphQL::Query.new(test_schema, document: doc, context: data) + result = query.result.to_h + + expect(result.dig('data', 'x', 'ok')).to eq({ 'oddValue' => 1 }) + expect(result.dig('data', 'x', 'bad')).to be_nil + end + + it 'paginates before scoping' do + # Inactive first so they sort first + n = 3 + inactive = create_list(:user, n - 1, state: :deactivated) + active_users = create_list(:user, 2, state: :active) + + data = { user_ids: [*inactive, *active_users].map(&:id) } + + doc = GraphQL.parse(<<~GQL) + query { + users(first: #{n}) { + pageInfo { hasNextPage } + nodes { name } } + } + GQL + + query = GraphQL::Query.new(test_schema, document: doc, context: data) + result = query.result.to_h + + # We expect the page to be loaded and then filtered - i.e. to have all + # deactivated users removed. + expect(result.dig('data', 'users', 'pageInfo', 'hasNextPage')).to be_truthy + expect(result.dig('data', 'users', 'nodes')) + .to contain_exactly({ 'name' => active_users.first.name }) + end + end +end diff --git a/spec/graphql/types/board_type_spec.rb b/spec/graphql/types/board_type_spec.rb index dca3cfd8aaf..403fbe1f290 100644 --- a/spec/graphql/types/board_type_spec.rb +++ b/spec/graphql/types/board_type_spec.rb @@ -8,8 +8,18 @@ RSpec.describe GitlabSchema.types['Board'] do specify { expect(described_class).to require_graphql_authorizations(:read_issue_board) } it 'has specific fields' do - expected_fields = %w[id name web_url web_path] + expected_fields = %w[ + id + name + hideBacklogList + hideClosedList + createdAt + updatedAt + lists + webPath + webUrl + ] - expect(described_class).to include_graphql_fields(*expected_fields) + expect(described_class).to have_graphql_fields(*expected_fields).at_least end end diff --git a/spec/graphql/types/boards/board_issue_input_type_spec.rb b/spec/graphql/types/boards/board_issue_input_type_spec.rb index 6319ff9a88e..5d3efb9b40d 100644 --- a/spec/graphql/types/boards/board_issue_input_type_spec.rb +++ b/spec/graphql/types/boards/board_issue_input_type_spec.rb @@ -5,9 +5,9 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['BoardIssueInput'] do it { expect(described_class.graphql_name).to eq('BoardIssueInput') } - it 'exposes negated issue arguments' do + it 'has specific fields' do allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername - releaseTag myReactionEmoji not search) + releaseTag myReactionEmoji not search assigneeWildcardId) expect(described_class.arguments.keys).to include(*allowed_args) expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType) diff --git a/spec/graphql/types/ci/job_status_enum_spec.rb b/spec/graphql/types/ci/job_status_enum_spec.rb new file mode 100644 index 00000000000..e8a1a2e0aa8 --- /dev/null +++ b/spec/graphql/types/ci/job_status_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiJobStatus'] do + it 'exposes all job status values' do + expect(described_class.values.values).to contain_exactly( + *::Ci::HasStatus::AVAILABLE_STATUSES.map do |status| + have_attributes(value: status, graphql_name: status.upcase) + end + ) + end +end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index 25f626cea0f..787e2174070 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -8,14 +8,32 @@ RSpec.describe Types::Ci::JobType do it 'exposes the expected fields' do expected_fields = %i[ - pipeline + active + allow_failure + artifacts + cancelable + commitPath + coverage + created_at + detailedStatus + duration + finished_at + id name needs - detailedStatus + pipeline + playable + queued_at + refName + refPath + retryable scheduledAt - artifacts - finished_at - duration + schedulingType + shortSha + stage + started_at + status + tags ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index e0e84a1b635..c7d2cbdb765 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -11,8 +11,9 @@ RSpec.describe Types::Ci::PipelineType do expected_fields = %w[ id iid sha before_sha status detailed_status config_source duration coverage created_at updated_at started_at finished_at committed_at - stages user retryable cancelable jobs source_job downstream - upstream path project active user_permissions warnings commit_path + stages user retryable cancelable jobs source_job job downstream + upstream path project active user_permissions warnings commit_path uses_needs + test_report_summary test_suite ] if Gitlab.ee? diff --git a/spec/graphql/types/ci/recent_failures_type_spec.rb b/spec/graphql/types/ci/recent_failures_type_spec.rb new file mode 100644 index 00000000000..38369da46bf --- /dev/null +++ b/spec/graphql/types/ci/recent_failures_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::RecentFailuresType do + specify { expect(described_class.graphql_name).to eq('RecentFailures') } + + it 'contains attributes related to a recent failure history for a test case' do + expected_fields = %w[ + count base_branch + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/stage_type_spec.rb b/spec/graphql/types/ci/stage_type_spec.rb index 9a8d4fa96a3..cb8c1cb02cd 100644 --- a/spec/graphql/types/ci/stage_type_spec.rb +++ b/spec/graphql/types/ci/stage_type_spec.rb @@ -10,6 +10,7 @@ RSpec.describe Types::Ci::StageType do name groups detailedStatus + jobs ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/test_case_status_enum_spec.rb b/spec/graphql/types/ci/test_case_status_enum_spec.rb new file mode 100644 index 00000000000..ba2d1aefb20 --- /dev/null +++ b/spec/graphql/types/ci/test_case_status_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestCaseStatusEnum do + specify { expect(described_class.graphql_name).to eq('TestCaseStatus') } + + it 'exposes all test case status types' do + expect(described_class.values.keys).to eq( + ::Gitlab::Ci::Reports::TestCase::STATUS_TYPES + ) + end +end diff --git a/spec/graphql/types/ci/test_case_type_spec.rb b/spec/graphql/types/ci/test_case_type_spec.rb new file mode 100644 index 00000000000..e6cd70c287e --- /dev/null +++ b/spec/graphql/types/ci/test_case_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestCaseType do + specify { expect(described_class.graphql_name).to eq('TestCase') } + + it 'contains attributes related to a pipeline test case' do + expected_fields = %w[ + name status classname file attachment_url execution_time stack_trace system_output recent_failures + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/test_report_summary_type_spec.rb b/spec/graphql/types/ci/test_report_summary_type_spec.rb new file mode 100644 index 00000000000..06974da0b88 --- /dev/null +++ b/spec/graphql/types/ci/test_report_summary_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestReportSummaryType do + specify { expect(described_class.graphql_name).to eq('TestReportSummary') } + + it 'contains attributes related to a pipeline test report summary' do + expected_fields = %w[ + total test_suites + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/test_report_total_type_spec.rb b/spec/graphql/types/ci/test_report_total_type_spec.rb new file mode 100644 index 00000000000..e5b7b358edb --- /dev/null +++ b/spec/graphql/types/ci/test_report_total_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestReportTotalType do + specify { expect(described_class.graphql_name).to eq('TestReportTotal') } + + it 'contains attributes related to a pipeline test report summary' do + expected_fields = %w[ + time count success failed skipped error suite_error + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/test_suite_summary_type_spec.rb b/spec/graphql/types/ci/test_suite_summary_type_spec.rb new file mode 100644 index 00000000000..e87782037c7 --- /dev/null +++ b/spec/graphql/types/ci/test_suite_summary_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestSuiteSummaryType do + specify { expect(described_class.graphql_name).to eq('TestSuiteSummary') } + + it 'contains attributes related to a pipeline test report summary' do + expected_fields = %w[ + name total_time total_count success_count failed_count skipped_count error_count suite_error build_ids + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/test_suite_type_spec.rb b/spec/graphql/types/ci/test_suite_type_spec.rb new file mode 100644 index 00000000000..d9caca3e2c3 --- /dev/null +++ b/spec/graphql/types/ci/test_suite_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::TestSuiteType do + specify { expect(described_class.graphql_name).to eq('TestSuite') } + + it 'contains attributes related to a pipeline test suite' do + expected_fields = %w[ + name total_time total_count success_count failed_count skipped_count error_count suite_error test_cases + ] + + expect(described_class).to have_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 8eb023ad2a3..4df51dc8d1b 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Types::GlobalIDType do let_it_be(:project) { create(:project) } + let(:gid) { project.to_global_id } it 'is has the correct name' do diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index 21fc530149c..6908a610aae 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -18,7 +18,7 @@ RSpec.describe GitlabSchema.types['Issue'] do confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status design_collection alert_management_alert severity current_user_todos moved moved_to - create_note_email] + create_note_email timelogs] fields.each do |field_name| expect(described_class).to have_graphql_field(field_name) diff --git a/spec/graphql/types/merge_request_review_state_enum_spec.rb b/spec/graphql/types/merge_request_review_state_enum_spec.rb new file mode 100644 index 00000000000..486e1c4f502 --- /dev/null +++ b/spec/graphql/types/merge_request_review_state_enum_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MergeRequestReviewState'] do + it 'the correct enum members' do + expect(described_class.values).to match( + 'REVIEWED' => have_attributes( + description: 'The merge request is reviewed.', + value: 'reviewed' + ), + 'UNREVIEWED' => have_attributes( + description: 'The merge request is unreviewed.', + value: 'unreviewed' + ) + ) + end +end diff --git a/spec/graphql/types/merge_requests/reviewer_type_spec.rb b/spec/graphql/types/merge_requests/reviewer_type_spec.rb new file mode 100644 index 00000000000..c2182e9968c --- /dev/null +++ b/spec/graphql/types/merge_requests/reviewer_type_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MergeRequestReviewer'] do + specify { expect(described_class).to require_graphql_authorizations(:read_user) } + + it 'has the expected fields' do + expected_fields = %w[ + id + bot + user_permissions + snippets + name + username + email + publicEmail + avatarUrl + webUrl + webPath + todos + state + status + location + authoredMergeRequests + assignedMergeRequests + reviewRequestedMergeRequests + groupMemberships + groupCount + projectMemberships + starredProjects + callouts + merge_request_interaction + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end + + describe '#merge_request_interaction' do + subject { described_class.fields['mergeRequestInteraction'] } + + it 'returns the correct type' do + is_expected.to have_graphql_type(Types::UserMergeRequestInteractionType) + end + + it 'has the correct arguments' do + is_expected.to have_attributes(arguments: be_empty) + end + end +end diff --git a/spec/graphql/types/milestone_type_spec.rb b/spec/graphql/types/milestone_type_spec.rb index 806495250ac..5c2ae5cea3c 100644 --- a/spec/graphql/types/milestone_type_spec.rb +++ b/spec/graphql/types/milestone_type_spec.rb @@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['Milestone'] do it 'has the expected fields' do expected_fields = %w[ - id title description state web_path + id iid title description state web_path due_date start_date created_at updated_at project_milestone group_milestone subgroup_milestone stats diff --git a/spec/graphql/types/packages/conan/file_metadatum_type_spec.rb b/spec/graphql/types/packages/conan/file_metadatum_type_spec.rb new file mode 100644 index 00000000000..18b17286654 --- /dev/null +++ b/spec/graphql/types/packages/conan/file_metadatum_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ConanFileMetadata'] do + it 'includes conan file metadatum fields' do + expected_fields = %w[ + id created_at updated_at recipe_revision package_revision conan_package_reference conan_file_type + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/packages/conan/metadatum_file_type_enum_spec.rb b/spec/graphql/types/packages/conan/metadatum_file_type_enum_spec.rb new file mode 100644 index 00000000000..379cb5168a8 --- /dev/null +++ b/spec/graphql/types/packages/conan/metadatum_file_type_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ConanMetadatumFileTypeEnum'] do + it 'uses all possible options from model' do + expected_keys = ::Packages::Conan::FileMetadatum.conan_file_types + .keys + .map(&:upcase) + + expect(described_class.values.keys).to contain_exactly(*expected_keys) + end +end diff --git a/spec/graphql/types/packages/conan/metadatum_type_spec.rb b/spec/graphql/types/packages/conan/metadatum_type_spec.rb new file mode 100644 index 00000000000..f8f24ffc95a --- /dev/null +++ b/spec/graphql/types/packages/conan/metadatum_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ConanMetadata'] do + it 'includes conan metadatum fields' do + expected_fields = %w[ + id created_at updated_at package_username package_channel recipe recipe_path + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/packages/package_without_versions_type_spec.rb b/spec/graphql/types/packages/package_details_type_spec.rb index faa79e588d5..06093813315 100644 --- a/spec/graphql/types/packages/package_without_versions_type_spec.rb +++ b/spec/graphql/types/packages/package_details_type_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['PackageWithoutVersions'] do +RSpec.describe GitlabSchema.types['PackageDetailsType'] do it 'includes all the package fields' do expected_fields = %w[ - id name version created_at updated_at package_type tags project pipelines + id name version created_at updated_at package_type tags project pipelines versions package_files ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/packages/package_file_type_spec.rb b/spec/graphql/types/packages/package_file_type_spec.rb new file mode 100644 index 00000000000..8e20aea5220 --- /dev/null +++ b/spec/graphql/types/packages/package_file_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageFile'] do + it 'includes package file fields' do + expected_fields = %w[ + id file_name created_at updated_at size file_name download_path file_md5 file_sha1 file_sha256 file_metadata + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/packages/package_type_spec.rb b/spec/graphql/types/packages/package_type_spec.rb index 43289a019b3..544d6ddc3af 100644 --- a/spec/graphql/types/packages/package_type_spec.rb +++ b/spec/graphql/types/packages/package_type_spec.rb @@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['Package'] do id name version package_type created_at updated_at project - tags pipelines versions + tags pipelines metadata versions ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 9579ef8b99b..f2c4068f048 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -106,7 +106,8 @@ RSpec.describe GitlabSchema.types['Project'] do expect(secure_analyzers_prefix['type']).to eq('string') expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') expect(secure_analyzers_prefix['label']).to eq('Image prefix') - expect(secure_analyzers_prefix['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(secure_analyzers_prefix['defaultValue']) + .to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') expect(secure_analyzers_prefix['value']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') expect(secure_analyzers_prefix['size']).to eq('LARGE') expect(secure_analyzers_prefix['options']).to be_nil @@ -124,8 +125,8 @@ RSpec.describe GitlabSchema.types['Project'] do it "returns the project's sast configuration for analyzer variables" do analyzer = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first - expect(analyzer['name']).to eq('brakeman') - expect(analyzer['label']).to eq('Brakeman') + expect(analyzer['name']).to eq('bandit') + expect(analyzer['label']).to eq('Bandit') expect(analyzer['enabled']).to eq(true) end @@ -184,9 +185,11 @@ RSpec.describe GitlabSchema.types['Project'] do context 'when repository is accessible only by team members' do it "returns no configuration" do - project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED, - builds_access_level: ProjectFeature::DISABLED, - repository_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!( + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::PRIVATE + ) secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') expect(secure_analyzers_prefix).to be_nil @@ -240,6 +243,7 @@ RSpec.describe GitlabSchema.types['Project'] do :assignee_username, :reviewer_username, :milestone_title, + :not, :sort ) end @@ -342,8 +346,13 @@ RSpec.describe GitlabSchema.types['Project'] do let_it_be(:project) { create(:project, :public) } context 'when project has Jira imports' do - let_it_be(:jira_import1) { create(:jira_import_state, :finished, project: project, jira_project_key: 'AA', created_at: 2.days.ago) } - let_it_be(:jira_import2) { create(:jira_import_state, :finished, project: project, jira_project_key: 'BB', created_at: 5.days.ago) } + let_it_be(:jira_import1) do + create(:jira_import_state, :finished, project: project, jira_project_key: 'AA', created_at: 2.days.ago) + end + + let_it_be(:jira_import2) do + create(:jira_import_state, :finished, project: project, jira_project_key: 'BB', created_at: 5.days.ago) + end it 'retrieves the imports' do expect(subject).to contain_exactly(jira_import1, jira_import2) @@ -363,4 +372,11 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::Ci::AnalyticsType) } it { is_expected.to have_graphql_resolver(Resolvers::ProjectPipelineStatisticsResolver) } end + + describe 'jobs field' do + subject { described_class.fields['jobs'] } + + it { is_expected.to have_graphql_type(Types::Ci::JobType.connection_type) } + it { is_expected.to have_graphql_arguments(:statuses) } + end end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index cb8e875dbf4..d3dcdd260b0 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -98,6 +98,6 @@ RSpec.describe GitlabSchema.types['Query'] do describe 'package field' do subject { described_class.fields['package'] } - it { is_expected.to have_graphql_type(Types::Packages::PackageType) } + it { is_expected.to have_graphql_type(Types::Packages::PackageDetailsType) } end end diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb new file mode 100644 index 00000000000..f8647e4e964 --- /dev/null +++ b/spec/graphql/types/repository/blob_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Repository::BlobType do + specify { expect(described_class.graphql_name).to eq('RepositoryBlob') } + + specify { expect(described_class).to have_graphql_fields(:id, :oid, :name, :path, :web_path, :lfs_oid, :mode) } +end diff --git a/spec/graphql/types/repository_type_spec.rb b/spec/graphql/types/repository_type_spec.rb index e9199bd286e..fa1e54dfcfa 100644 --- a/spec/graphql/types/repository_type_spec.rb +++ b/spec/graphql/types/repository_type_spec.rb @@ -12,4 +12,8 @@ RSpec.describe GitlabSchema.types['Repository'] do specify { expect(described_class).to have_graphql_field(:tree) } specify { expect(described_class).to have_graphql_field(:exists, calls_gitaly?: true, complexity: 2) } + + specify { expect(described_class).to have_graphql_field(:blobs) } + + specify { expect(described_class).to have_graphql_field(:branch_names, calls_gitaly?: true, complexity: 170) } end diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb index 4d827186a9b..b87770ebe8d 100644 --- a/spec/graphql/types/snippet_type_spec.rb +++ b/spec/graphql/types/snippet_type_spec.rb @@ -161,6 +161,7 @@ RSpec.describe GitlabSchema.types['Snippet'] do describe '#blobs' do let_it_be(:snippet) { create(:personal_snippet, :public, author: user) } + let(:query_blobs) { subject.dig('data', 'snippets', 'nodes')[0].dig('blobs', 'nodes') } let(:paths) { [] } let(:query) do @@ -201,6 +202,7 @@ RSpec.describe GitlabSchema.types['Snippet'] do context 'when snippet has repository' do let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) } + let(:blobs) { snippet.blobs } it_behaves_like 'an array' diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb new file mode 100644 index 00000000000..38bd70d5097 --- /dev/null +++ b/spec/graphql/types/timelog_type_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Timelog'] do + let(:fields) { %i[spent_at time_spent user issue note] } + + 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) } + + describe 'user field' do + subject { described_class.fields['user'] } + + it 'returns user' do + is_expected.to have_non_null_graphql_type(Types::UserType) + end + end + + describe 'issue field' do + subject { described_class.fields['issue'] } + + it 'returns issue' do + is_expected.to have_graphql_type(Types::IssueType) + end + end + + describe 'note field' do + subject { described_class.fields['note'] } + + it 'returns note' do + is_expected.to have_graphql_type(Types::Notes::NoteType) + end + end +end diff --git a/spec/graphql/types/user_merge_request_interaction_type_spec.rb b/spec/graphql/types/user_merge_request_interaction_type_spec.rb new file mode 100644 index 00000000000..f424c9200ab --- /dev/null +++ b/spec/graphql/types/user_merge_request_interaction_type_spec.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + + let(:interaction) { ::Users::MergeRequestInteraction.new(user: user, merge_request: merge_request.reset) } + + specify { expect(described_class).to require_graphql_authorizations(:read_merge_request) } + + it 'has the expected fields' do + expected_fields = %w[ + can_merge + can_update + review_state + reviewed + approved + ] + + expect(described_class).to have_graphql_fields(*expected_fields).at_least + end + + def resolve(field_name) + resolve_field(field_name, interaction, current_user: current_user) + end + + describe '#can_merge' do + subject { resolve(:can_merge) } + + context 'when the user cannot merge' do + it { is_expected.to be false } + end + + context 'when the user can merge' do + before do + project.add_maintainer(user) + end + + it { is_expected.to be true } + end + end + + describe '#can_update' do + subject { resolve(:can_update) } + + context 'when the user cannot update the MR' do + it { is_expected.to be false } + end + + context 'when the user can update the MR' do + before do + project.add_developer(user) + end + + it { is_expected.to be true } + end + end + + describe '#review_state' do + subject { resolve(:review_state) } + + context 'when the user has not been asked to review the MR' do + it { is_expected.to be_nil } + + it 'implies not reviewed' do + expect(resolve(:reviewed)).to be false + end + end + + context 'when the user has been asked to review the MR' do + before do + merge_request.reviewers << user + end + + it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['UNREVIEWED'].value) } + + it 'implies not reviewed' do + expect(resolve(:reviewed)).to be false + end + end + + context 'when the user has provided a review' do + before do + merge_request.merge_request_reviewers.create!(reviewer: user, state: MergeRequestReviewer.states['reviewed']) + end + + it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['REVIEWED'].value) } + + it 'implies reviewed' do + expect(resolve(:reviewed)).to be true + end + end + end + + describe '#approved' do + subject { resolve(:approved) } + + context 'when the user has not approved the MR' do + it { is_expected.to be false } + end + + context 'when the user has approved the MR' do + before do + merge_request.approved_by_users << user + end + + it { is_expected.to be true } + end + end +end |