diff options
author | Douwe Maan <douwe@gitlab.com> | 2018-07-04 12:31:07 +0300 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2018-07-04 12:31:07 +0300 |
commit | 03f0f2566b3c536cf512b287e0d4d52d40a065c5 (patch) | |
tree | fd28c5029e51fa7240060a1a029903cc63ab3cc6 /spec | |
parent | d81ce8b25c50d96f47d6cdbcdfc3d1f66fca0f96 (diff) | |
parent | 04b046587fe609cd889f3fa518f08299abc61267 (diff) |
Merge branch 'bvl-graphql-pipeline-lists' into 'master'
Add pipeline lists to graphql
Closes #48469
See merge request gitlab-org/gitlab-ce!20249
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/projects/pipelines_controller_spec.rb | 20 | ||||
-rw-r--r-- | spec/finders/pipelines_finder_spec.rb | 29 | ||||
-rw-r--r-- | spec/graphql/gitlab_schema_spec.rb | 6 | ||||
-rw-r--r-- | spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb | 52 | ||||
-rw-r--r-- | spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb | 30 | ||||
-rw-r--r-- | spec/graphql/resolvers/project_pipelines_resolver_spec.rb | 22 | ||||
-rw-r--r-- | spec/graphql/types/ci/pipeline_type_spec.rb | 7 | ||||
-rw-r--r-- | spec/graphql/types/merge_request_type_spec.rb | 13 | ||||
-rw-r--r-- | spec/graphql/types/project_type_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb | 112 | ||||
-rw-r--r-- | spec/requests/api/graphql/project/merge_request_spec.rb | 24 | ||||
-rw-r--r-- | spec/requests/api/graphql/project_query_spec.rb | 12 | ||||
-rw-r--r-- | spec/support/helpers/graphql_helpers.rb | 20 |
13 files changed, 340 insertions, 9 deletions
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 9618a8417ec..1cc7f33b57a 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -4,7 +4,7 @@ describe Projects::PipelinesController do include ApiHelpers set(:user) { create(:user) } - set(:project) { create(:project, :public, :repository) } + let(:project) { create(:project, :public, :repository) } let(:feature) { ProjectFeature::DISABLED } before do @@ -91,6 +91,24 @@ describe Projects::PipelinesController do end end + context 'when the project is private' do + let(:project) { create(:project, :private, :repository) } + + it 'returns `not_found` when the user does not have access' do + sign_in(create(:user)) + + get_pipelines_index_json + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'returns the pipelines when the user has access' do + get_pipelines_index_json + + expect(json_response['pipelines'].size).to eq(5) + end + end + def get_pipelines_index_json get :index, namespace_id: project.namespace, project_id: project, diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index d6253b605b9..c6e832ad69b 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -1,9 +1,10 @@ require 'spec_helper' describe PipelinesFinder do - let(:project) { create(:project, :repository) } - - subject { described_class.new(project, params).execute } + let(:project) { create(:project, :public, :repository) } + let(:current_user) { nil } + let(:params) { {} } + subject { described_class.new(project, current_user, params).execute } describe "#execute" do context 'when params is empty' do @@ -223,5 +224,27 @@ describe PipelinesFinder do end end end + + context 'when the project has limited access to piplines' do + let(:project) { create(:project, :private, :repository) } + let(:current_user) { create(:user) } + let!(:pipelines) { create_list(:ci_pipeline, 2, project: project) } + + context 'when the user has access' do + before do + project.add_developer(current_user) + end + + it 'is expected to return pipelines' do + is_expected.to contain_exactly(*pipelines) + end + end + + context 'the user is not allowed to read pipelines' do + it 'returns empty' do + is_expected.to be_empty + end + end + end end end diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index b892f6b44ed..515bbe78cb7 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -27,6 +27,12 @@ describe GitlabSchema do expect(described_class.query).to eq(::Types::QueryType.to_graphql) end + it 'paginates active record relations using `Gitlab::Graphql::Connections::KeysetConnection`' do + connection = GraphQL::Relay::BaseConnection::CONNECTION_IMPLEMENTATIONS[ActiveRecord::Relation.name] + + expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection) + end + def field_instrumenters described_class.instrumenters[:field] end diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb new file mode 100644 index 00000000000..ea7159eacf9 --- /dev/null +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe ResolvesPipelines do + include GraphqlHelpers + + subject(:resolver) do + Class.new(Resolvers::BaseResolver) do + include ResolvesPipelines + + def resolve(**args) + resolve_pipelines(object, args) + end + end + end + + let(:current_user) { create(:user) } + set(:project) { create(:project, :private) } + set(:pipeline) { create(:ci_pipeline, project: project) } + set(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + set(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } + set(:sha_pipeline) { create(:ci_pipeline, project: project, sha: 'deadbeef') } + + before do + project.add_developer(current_user) + end + + it { is_expected.to have_graphql_arguments(:status, :ref, :sha) } + + it 'finds all pipelines' do + expect(resolve_pipelines).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline) + end + + it 'allows filtering by status' do + expect(resolve_pipelines(status: 'failed')).to contain_exactly(failed_pipeline) + end + + it 'allows filtering by ref' do + expect(resolve_pipelines(ref: 'awesome-feature')).to contain_exactly(ref_pipeline) + end + + it 'allows filtering by sha' do + expect(resolve_pipelines(sha: 'deadbeef')).to contain_exactly(sha_pipeline) + end + + it 'does not return any pipelines if the user does not have access' do + expect(resolve_pipelines({}, {})).to be_empty + end + + def resolve_pipelines(args = {}, context = { current_user: current_user }) + resolve(resolver, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb new file mode 100644 index 00000000000..09b17bf6fc9 --- /dev/null +++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Resolvers::MergeRequestPipelinesResolver do + include GraphqlHelpers + + set(:merge_request) { create(:merge_request) } + set(:pipeline) do + create( + :ci_pipeline, + project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha + ) + end + set(:other_project_pipeline) { create(:ci_pipeline, project: merge_request.source_project) } + set(:other_pipeline) { create(:ci_pipeline) } + let(:current_user) { create(:user) } + + before do + merge_request.project.add_developer(current_user) + end + + def resolve_pipelines + resolve(described_class, obj: merge_request, ctx: { current_user: current_user }) + end + + it 'resolves only MRs for the passed merge request' do + expect(resolve_pipelines).to contain_exactly(pipeline) + end +end diff --git a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb new file mode 100644 index 00000000000..407ca2f9d78 --- /dev/null +++ b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Resolvers::ProjectPipelinesResolver do + include GraphqlHelpers + + set(:project) { create(:project) } + set(:pipeline) { create(:ci_pipeline, project: project) } + set(:other_pipeline) { create(:ci_pipeline) } + let(:current_user) { create(:user) } + + before do + project.add_developer(current_user) + end + + def resolve_pipelines + resolve(described_class, obj: project, ctx: { current_user: current_user }) + end + + it 'resolves only MRs for the passed merge request' do + expect(resolve_pipelines).to contain_exactly(pipeline) + end +end diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb new file mode 100644 index 00000000000..ec1c689a4be --- /dev/null +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe Types::Ci::PipelineType do + it { expect(described_class.graphql_name).to eq('Pipeline') } + + it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Pipeline) } +end diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 6e57122867a..c369953e3ea 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -1,5 +1,16 @@ require 'spec_helper' -describe Types::MergeRequestType do +describe GitlabSchema.types['MergeRequest'] do it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::MergeRequest) } + + describe 'head pipeline' do + it 'has a head pipeline field' do + expect(described_class).to have_graphql_field(:head_pipeline) + end + + it 'authorizes the field' do + expect(described_class.fields['headPipeline']) + .to require_graphql_authorizations(:read_pipeline) + end + end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 7b5bc335511..49606c397b9 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -13,4 +13,6 @@ describe GitlabSchema.types['Project'] do .to require_graphql_authorizations(:read_merge_request) end end + + it { is_expected.to have_graphql_field(:pipelines) } end diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb new file mode 100644 index 00000000000..96615ae80de --- /dev/null +++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +describe Gitlab::Graphql::Connections::KeysetConnection do + let(:nodes) { Project.all.order(id: :asc) } + let(:arguments) { {} } + subject(:connection) do + described_class.new(nodes, arguments, max_page_size: 3) + end + + def encoded_property(value) + Base64.strict_encode64(value.to_s) + end + + describe '#cursor_from_nodes' do + let(:project) { create(:project) } + + it 'returns an encoded ID' do + expect(connection.cursor_from_node(project)) + .to eq(encoded_property(project.id)) + end + + context 'when an order was specified' do + let(:nodes) { Project.order(:updated_at) } + + it 'returns the encoded value of the order' do + expect(connection.cursor_from_node(project)) + .to eq(encoded_property(project.updated_at)) + end + end + end + + describe '#sliced_nodes' do + let(:projects) { create_list(:project, 4) } + + context 'when before is passed' do + let(:arguments) { { before: encoded_property(projects[1].id) } } + + it 'only returns the project before the selected one' do + expect(subject.sliced_nodes).to contain_exactly(projects.first) + end + + context 'when the sort order is descending' do + let(:nodes) { Project.all.order(id: :desc) } + + it 'returns the correct nodes' do + expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) + end + end + end + + context 'when after is passed' do + let(:arguments) { { after: encoded_property(projects[1].id) } } + + it 'only returns the project before the selected one' do + expect(subject.sliced_nodes).to contain_exactly(*projects[2..-1]) + end + + context 'when the sort order is descending' do + let(:nodes) { Project.all.order(id: :desc) } + + it 'returns the correct nodes' do + expect(subject.sliced_nodes).to contain_exactly(projects.first) + end + end + end + + context 'when both before and after are passed' do + let(:arguments) do + { + after: encoded_property(projects[1].id), + before: encoded_property(projects[3].id) + } + end + + it 'returns the expected set' do + expect(subject.sliced_nodes).to contain_exactly(projects[2]) + end + end + end + + describe '#paged_nodes' do + let!(:projects) { create_list(:project, 5) } + + it 'returns the collection limited to max page size' do + expect(subject.paged_nodes.size).to eq(3) + end + + context 'when `first` is passed' do + let(:arguments) { { first: 2 } } + + it 'returns only the first elements' do + expect(subject.paged_nodes).to contain_exactly(projects.first, projects.second) + end + end + + context 'when `last` is passed' do + let(:arguments) { { last: 2 } } + + it 'returns only the last elements' do + expect(subject.paged_nodes).to contain_exactly(projects[3], projects[4]) + end + end + + context 'when both are passed' do + let(:arguments) { { first: 2, last: 2 } } + + it 'raises an error' do + expect { subject.paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + end + end +end diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index ad57c43bc87..deb6abbc026 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -67,4 +67,28 @@ describe 'getting merge request information nested in a project' do expect(merge_request_graphql_data).to be_nil end end + + context 'when there are pipelines' do + before do + pipeline = create( + :ci_pipeline, + project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha + ) + merge_request.update!(head_pipeline: pipeline) + end + + it 'has a head pipeline' do + post_graphql(query, current_user: current_user) + + expect(merge_request_graphql_data['headPipeline']).to be_present + end + + it 'has pipeline connections' do + post_graphql(query, current_user: current_user) + + expect(merge_request_graphql_data['pipelines']['edges'].size).to eq(1) + end + end end diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb index a2b3dc5d121..0727ada4691 100644 --- a/spec/requests/api/graphql/project_query_spec.rb +++ b/spec/requests/api/graphql/project_query_spec.rb @@ -26,6 +26,18 @@ describe 'getting project information' do post_graphql(query, current_user: current_user) end end + + context 'when there are pipelines present' do + before do + create(:ci_pipeline, project: project) + end + + it 'is included in the pipelines connection' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['pipelines']['edges'].size).to eq(1) + end + end end context 'when the user does not have access to the project' do diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 0930b9da368..b9322975b5a 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -57,12 +57,12 @@ module GraphqlHelpers type.fields.map do |name, field| # We can't guess arguments, so skip fields that require them - next if field.arguments.any? + next if required_arguments?(field) - if scalar?(field) - name - else + if nested_fields?(field) "#{name} { #{all_graphql_fields_for(field_type(field))} }" + else + name end end.compact.join("\n") end @@ -85,10 +85,22 @@ module GraphqlHelpers json_response['data'] end + def nested_fields?(field) + !scalar?(field) && !enum?(field) + end + def scalar?(field) field_type(field).kind.scalar? end + def enum?(field) + field_type(field).kind.enum? + end + + def required_arguments?(field) + field.arguments.values.any? { |argument| argument.type.non_null? } + end + def field_type(field) if field.type.respond_to?(:of_type) field.type.of_type |