Commit 8dfcb66d authored by Alex Kalderimis's avatar Alex Kalderimis

[GQL] Merge Request resolvers

This splits up the merge request resolver into a single form and a
plural form, with different arguments and purposes.

The single form supports a single required argument: `iid`

The plural form is meant for search and filtering and supports:

- source_branches (implicit any)
- target_branches (implicit any)
- labels (implicit all)
- state (only one state at a time)
parent 30dfef5d
......@@ -7,8 +7,15 @@ module Mutations
def resolve_issuable(type:, parent_path:, iid:)
parent = resolve_issuable_parent(type, parent_path)
key = type == :merge_request ? :iids : :iid
args = { key => iid.to_s }
issuable_resolver(type, parent, context).resolve(iid: iid.to_s)
resolver = issuable_resolver(type, parent, context)
ready, early_return = resolver.ready?(**args)
return early_return unless ready
resolver.resolve(**args)
end
private
......
# frozen_string_literal: true
# Mixin for resolving merge requests. All arguments must be in forms
# that `MergeRequestsFinder` can handle, so you may need to use aliasing.
module ResolvesMergeRequests
extend ActiveSupport::Concern
included do
type Types::MergeRequestType, null: true
end
def resolve(**args)
args[:iids] = Array.wrap(args[:iids]) if args[:iids]
args.compact!
if args.keys == [:iids]
batch_load_merge_requests(args[:iids])
else
args[:project_id] = project.id
MergeRequestsFinder.new(current_user, args).execute
end.then(&(single? ? :first : :itself))
end
def ready?(**args)
return early_return if no_results_possible?(args)
super
end
def early_return
[false, single? ? nil : MergeRequest.none]
end
private
def batch_load_merge_requests(iids)
iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader
end
# rubocop: disable CodeReuse/ActiveRecord
def batch_load(iid)
BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
args[:key].merge_requests.where(iid: iids).each do |mr|
loader.call(mr.iid.to_s, mr)
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
# frozen_string_literal: true
module Resolvers
class MergeRequestResolver < BaseResolver.single
include ResolvesMergeRequests
alias_method :project, :synchronized_object
argument :iid, GraphQL::STRING_TYPE,
required: true,
as: :iids,
description: 'IID of the merge request, for example `1`'
def no_results_possible?(args)
project.nil?
end
end
end
......@@ -2,47 +2,39 @@
module Resolvers
class MergeRequestsResolver < BaseResolver
argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'IID of the merge request, for example `1`'
include ResolvesMergeRequests
alias_method :project, :synchronized_object
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'Array of IIDs of merge requests, for example `[1, 2]`'
type Types::MergeRequestType, null: true
alias_method :project, :object
argument :source_branches, [GraphQL::STRING_TYPE],
required: false,
as: :source_branch,
description: 'Array of source branch names. All resolved merge requests will have one of these branches as their source.'
def resolve(**args)
project = object.respond_to?(:sync) ? object.sync : object
return MergeRequest.none if project.nil?
argument :target_branches, [GraphQL::STRING_TYPE],
required: false,
as: :target_branch,
description: 'Array of target branch names. All resolved merge requests will have one of these branches as their target.'
args[:iids] ||= [args[:iid]].compact
argument :state, ::Types::MergeRequestStateEnum,
required: false,
description: 'A merge request state. If provided, all resolved merge requests will have this state.'
if args[:iids].any?
batch_load_merge_requests(args[:iids])
else
args[:project_id] = project.id
argument :labels, [GraphQL::STRING_TYPE],
required: false,
as: :label_name,
description: 'Array of label names. All resolved merge requests will have all of these labels.'
MergeRequestsFinder.new(context[:current_user], args).execute
end
def self.single
::Resolvers::MergeRequestResolver
end
def batch_load_merge_requests(iids)
iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader
end
# rubocop: disable CodeReuse/ActiveRecord
def batch_load(iid)
BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key]
arg_key.merge_requests.where(iid: iids).each do |mr|
loader.call(mr.iid.to_s, mr)
end
end
def no_results_possible?(args)
project.nil? || args.values.any? { |v| v.is_a?(Array) && v.empty? }
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
---
title: Add filters to merge request fields
merge_request: 32328
author:
type: added
......@@ -7871,12 +7871,7 @@ type Project {
"""
IID of the merge request, for example `1`
"""
iid: String
"""
Array of IIDs of merge requests, for example `[1, 2]`
"""
iids: [String!]
iid: String!
): MergeRequest
"""
......@@ -7899,19 +7894,34 @@ type Project {
first: Int
"""
IID of the merge request, for example `1`
Array of IIDs of merge requests, for example `[1, 2]`
"""
iid: String
iids: [String!]
"""
Array of IIDs of merge requests, for example `[1, 2]`
Array of label names. All resolved merge requests will have all of these labels.
"""
iids: [String!]
labels: [String!]
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Array of source branch names. All resolved merge requests will have one of these branches as their source.
"""
sourceBranches: [String!]
"""
A merge request state. If provided, all resolved merge requests will have this state.
"""
state: MergeRequestState
"""
Array of target branch names. All resolved merge requests will have one of these branches as their target.
"""
targetBranches: [String!]
): MergeRequestConnection
"""
......
......@@ -23270,26 +23270,12 @@
"name": "iid",
"description": "IID of the merge request, for example `1`",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "iids",
"description": "Array of IIDs of merge requests, for example `[1, 2]`",
"type": {
"kind": "LIST",
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
......@@ -23308,18 +23294,72 @@
"description": "Merge requests of the project",
"args": [
{
"name": "iid",
"description": "IID of the merge request, for example `1`",
"name": "iids",
"description": "Array of IIDs of merge requests, for example `[1, 2]`",
"type": {
"kind": "SCALAR",
"name": "String",
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "sourceBranches",
"description": "Array of source branch names. All resolved merge requests will have one of these branches as their source.",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "targetBranches",
"description": "Array of target branch names. All resolved merge requests will have one of these branches as their target.",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "state",
"description": "A merge request state. If provided, all resolved merge requests will have this state.",
"type": {
"kind": "ENUM",
"name": "MergeRequestState",
"ofType": null
},
"defaultValue": null
},
{
"name": "iids",
"description": "Array of IIDs of merge requests, for example `[1, 2]`",
"name": "labels",
"description": "Array of label names. All resolved merge requests will have all of these labels.",
"type": {
"kind": "LIST",
"name": null,
......@@ -84,7 +84,7 @@ module Gitlab
elsif resolved_type.is_a? Array
# A simple list of rendered types each object being an object to authorize
resolved_type.select do |single_object_type|
allowed_access?(current_user, single_object_type.object)
allowed_access?(current_user, unpromise(single_object_type).object)
end
else
raise "Can't authorize #{@field}"
......@@ -113,6 +113,17 @@ module Gitlab
def scalar_type?
node_type_for_basic_connection(@field.type).kind.scalar?
end
# Sometimes we get promises, and have to resolve them. The dedicated way
# of doing this (GitlabSchema.after_lazy) is a private framework method,
# and so we use duck-typing interface inference here instead.
def unpromise(maybe_promise)
if maybe_promise.respond_to?(:value) && !maybe_promise.respond_to?(:object)
maybe_promise.value
else
maybe_promise
end
end
end
end
end
......
......@@ -133,6 +133,11 @@ FactoryBot.define do
end
end
trait :unique_branches do
source_branch { generate(:branch) }
target_branch { generate(:branch) }
end
trait :with_coverage_reports do
after(:build) do |merge_request|
merge_request.head_pipeline = build(
......
......@@ -6,6 +6,24 @@ describe MergeRequestsFinder do
context "multiple projects with merge requests" do
include_context 'MergeRequestsFinder multiple projects with merge requests context'
shared_examples 'scalar or array parameter' do
let(:values) { merge_requests.pluck(attribute) }
let(:params) { {} }
let(:key) { attribute }
it 'takes scalar values' do
found = described_class.new(user, params.merge(key => values.first)).execute
expect(found).to contain_exactly(merge_requests.first)
end
it 'takes array values' do
found = described_class.new(user, params.merge(key => values)).execute
expect(found).to match_array(merge_requests)
end
end
describe '#execute' do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
......@@ -91,28 +109,56 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request5)
end
it 'filters by iid' do
params = { project_id: project1.id, iids: merge_request1.iid }
describe ':iid parameter' do
it_behaves_like 'scalar or array parameter' do
let(:params) { { project_id: project1.id } }
let(:merge_requests) { [merge_request1, merge_request2] }
let(:key) { :iids }
let(:attribute) { :iid }
end
end
merge_requests = described_class.new(user, params).execute
[:source_branch, :target_branch].each do |param|
describe "#{param} parameter" do
let(:merge_requests) { create_list(:merge_request, 2, :unique_branches, source_project: project4, target_project: project4, author: user) }
let(:attribute) { param }
expect(merge_requests).to contain_exactly(merge_request1)
it_behaves_like 'scalar or array parameter'
end
end
it 'filters by source branch' do
params = { source_branch: merge_request2.source_branch }
describe ':label_name parameter' do
let(:common_labels) { create_list(:label, 3) }
let(:distinct_labels) { create_list(:label, 3) }
let(:merge_requests) do
common_attrs = {
source_project: project1, target_project: project1, author: user
}
distinct_labels.map do |label|
labels = [label, *common_labels]
create(:labeled_merge_request, :closed, labels: labels, **common_attrs)
end
end
merge_requests = described_class.new(user, params).execute
def find(label_name)
described_class.new(user, label_name: label_name).execute
end
expect(merge_requests).to contain_exactly(merge_request2)
end
it 'accepts a single label' do
found = find(distinct_labels.first.title)
common = find(common_labels.first.title)
it 'filters by target branch' do
params = { target_branch: merge_request2.target_branch }
expect(found).to contain_exactly(merge_requests.first)
expect(common).to match_array(merge_requests)
end
merge_requests = described_class.new(user, params).execute
it 'accepts an array of labels, all of which must match' do
all_distinct = find(distinct_labels.pluck(:title))
all_common = find(common_labels.pluck(:title))
expect(merge_requests).to contain_exactly(merge_request2)
expect(all_distinct).to be_empty
expect(all_common).to match_array(merge_requests)
end
end
it 'filters by source project id' do
......@@ -158,7 +204,10 @@ describe MergeRequestsFinder do
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4)
expect(merge_requests).to contain_exactly(
merge_request1, merge_request2, merge_request3, merge_request4,
merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3,
wip_merge_request4)
end
it 'adds wip to scalar params' do
......
......@@ -6,61 +6,164 @@ describe Resolvers::MergeRequestsResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:merge_request_1) { create(:merge_request, :simple, source_project: project, target_project: project) }
let_it_be(:merge_request_2) { create(:merge_request, :rebased, source_project: project, target_project: project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:common_attrs) { { author: current_user, source_project: project, target_project: project } }
let_it_be(:merge_request_1) { create(:merge_request, :simple, **common_attrs) }
let_it_be(:merge_request_2) { create(:merge_request, :rebased, **common_attrs) }
let_it_be(:merge_request_3) { create(:merge_request, :unique_branches, **common_attrs) }
let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) }
let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) }
let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2), **common_attrs) }
let_it_be(:other_project) { create(:project, :repository) }
let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) }
let(:iid_1) { merge_request_1.iid }
let(:iid_2) { merge_request_2.iid }
let(:other_iid) { other_merge_request.iid }
before do
project.add_developer(current_user)
end
describe '#resolve' do
it 'batch-resolves by target project full path and individual IID' do
result = batch_sync(max_queries: 2) do
resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2)
context 'no arguments' do
it 'returns all merge requests' do
result = resolve_mr(project, {})
expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6)
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
it 'returns only merge requests that the current user can see' do
result = resolve_mr(project, {}, user: build(:user))
expect(result).to be_empty
end
end
it 'batch-resolves by target project full path and IIDS' do
result = batch_sync(max_queries: 2) do
resolve_mr(project, iids: [iid_1, iid_2])
context 'by iid alone' do
it 'batch-resolves by target project full path and individual IID' do
result = batch_sync(max_queries: 2) do
[iid_1, iid_2].map { |iid| resolve_mr_single(project, iid) }
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
it 'batch-resolves by target project full path and IIDS' do
result = batch_sync(max_queries: 2) do
resolve_mr(project, iids: [iid_1, iid_2])
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
end
it 'can batch-resolve merge requests from different projects' do
result = batch_sync(max_queries: 3) do
resolve_mr(project, iids: iid_1) +
resolve_mr(project, iids: iid_2) +
resolve_mr(other_project, iids: other_iid)
end
expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
end
it 'resolves an unknown iid to be empty' do
result = batch_sync { resolve_mr_single(project, -1) }
expect(result).to be_nil
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
it 'resolves empty iids to be empty' do
result = batch_sync { resolve_mr(project, iids: []) }
expect(result).to be_empty
end
it 'resolves an unknown project to be nil when single' do
result = batch_sync { resolve_mr_single(nil, iid_1) }
expect(result).to be_nil
end
it 'resolves an unknown project to be empty' do
result = batch_sync { resolve_mr(nil, iids: [iid_1]) }
expect(result).to be_empty
end
end
it 'can batch-resolve merge requests from different projects' do
result = batch_sync(max_queries: 3) do
resolve_mr(project, iid: iid_1) +
resolve_mr(project, iid: iid_2) +
resolve_mr(other_project, iid: other_iid)
context 'by source branches' do
it 'takes one argument' do
result = resolve_mr(project, source_branch: [merge_request_3.source_branch])
expect(result).to contain_exactly(merge_request_3)
end
expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request)
it 'takes more than one argument' do
mrs = [merge_request_3, merge_request_4]
branches = mrs.map(&:source_branch)
result = resolve_mr(project, source_branch: branches )
expect(result).to match_array(mrs)
end
end
it 'resolves an unknown iid to be empty' do
result = batch_sync { resolve_mr(project, iid: -1) }
context 'by target branches' do
it 'takes one argument' do
result = resolve_mr(project, target_branch: [merge_request_3.target_branch])
expect(result).to contain_exactly(merge_request_3)
end
expect(result.compact).to be_empty
it 'takes more than one argument' do
mrs = [merge_request_3, merge_request_4]
branches = mrs.map(&:target_branch)
result = resolve_mr(project, target_branch: branches )
expect(result.compact).to match_array(mrs)
end
end
it 'resolves empty iids to be empty' do
result = batch_sync { resolve_mr(project, iids: []) }
context 'by state' do
it 'takes one argument' do
result = resolve_mr(project, state: 'locked')
expect(result).to be_empty
expect(result).to contain_exactly(merge_request_4, merge_request_5)
end
end
it 'resolves an unknown project to be empty' do
result = batch_sync { resolve_mr(nil, iid: iid_1) }
context 'by label' do
let_it_be(:label) { merge_request_6.labels.first }
let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) }
expect(result.compact).to be_empty
it 'takes one argument' do
result = resolve_mr(project, label_name: [label.title])
expect(result).to contain_exactly(merge_request_6, with_label)
end
it 'takes multiple arguments, with semantics of ALL MUST MATCH' do
result = resolve_mr(project, label_name: merge_request_6.labels.map(&:title))
expect(result).to contain_exactly(merge_request_6)
end
end
describe 'combinations' do
it 'requires all filters' do
create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch)
result = resolve_mr(project, source_branch: [merge_request_4.source_branch], state: 'locked')
expect(result.compact).to contain_exactly(merge_request_4)
end
end
end
def resolve_mr(project, args)
resolve(described_class, obj: project, args: args)
def resolve_mr_single(project, iid)
resolve_mr(project, { iids: iid }, resolver: described_class.single)
end
def resolve_mr(project, args, resolver: described_class, user: current_user)
resolve(resolver, obj: project, args: args, ctx: { current_user: user })
end
end
......@@ -45,18 +45,32 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_resolver(Resolvers::IssuesResolver) }
end
describe 'merge_requests field' do
describe 'merge_request field' do
subject { described_class.fields['mergeRequest'] }
it { is_expected.to have_graphql_type(Types::MergeRequestType) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver.single) }
it { is_expected.to have_graphql_arguments(:iid) }
end
describe 'merge_request field' do
describe 'merge_requests field' do
subject { described_class.fields['mergeRequests'] }
it { is_expected.to have_graphql_type(Types::MergeRequestType.connection_type) }
it { is_expected.to have_graphql_resolver(Resolvers::MergeRequestsResolver) }
it do
is_expected.to have_graphql_arguments(:iids,
:source_branches,
:target_branches,
:state,
:labels,
:before,
:after,
:first,
:last
)
end
end
describe 'snippets field' do
......
# frozen_string_literal: true
require 'spec_helper'
describe 'getting merge request listings nested in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:current_user) { create(:user) }
let_it_be(:label) { create(:label) }
let_it_be(:merge_request_a) { create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) }
let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) }
let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) }
let_it_be(:merge_request_d) { create(:merge_request, :locked, :unique_branches, source_project: project) }
let(:results) { graphql_data.dig('project', 'mergeRequests', 'nodes') }
let(:search_params) { nil }
let(:query) do
graphql_query_for(
'project',
{ 'fullPath' => project.full_path },
query_graphql_field('mergeRequests', search_params, [
query_graphql_field('nodes', nil, all_graphql_fields_for('MergeRequest', max_depth: 1))
])
)
end
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
end
shared_examples 'searching with parameters' do
let(:expected) do
mrs.map { |mr| a_hash_including('iid' => mr.iid.to_s, 'title' => mr.title) }
end
it 'finds the right mrs' do
post_graphql(query, current_user: current_user)
expect(results).to match_array(expected)
end
end
context 'there are no search params' do
let(:search_params) { nil }
let(:mrs) { [merge_request_a, merge_request_b, merge_request_c, merge_request_d] }
it_behaves_like 'searching with parameters'
end
context 'the search params do not match anything' do
let(:search_params) { { iids: %w(foo bar baz) } }
let(:mrs) { [] }
it_behaves_like 'searching with parameters'
end
context 'searching by iids' do
let(:search_params) { { iids: mrs.map(&:iid).map(&:to_s) } }
let(:mrs) { [merge_request_a, merge_request_c] }
it_behaves_like 'searching with parameters'
end
context 'searching by state' do
let(:search_params) { { state: :closed } }
let(:mrs) { [merge_request_b, merge_request_c] }
it_behaves_like 'searching with parameters'
end
context 'searching by source_branch' do
let(:search_params) { { source_branches: mrs.map(&:source_branch) } }
let(:mrs) { [merge_request_b, merge_request_c] }
it_behaves_like 'searching with parameters'
end
context 'searching by target_branch' do
let(:search_params) { { target_branches: mrs.map(&:target_branch) } }
let(:mrs) { [merge_request_a, merge_request_d] }
it_behaves_like 'searching with parameters'
end
context 'searching by label' do
let(:search_params) { { labels: [label.title] } }
let(:mrs) { [merge_request_a, merge_request_c] }
it_behaves_like 'searching with parameters'
end
context 'searching by combination' do
let(:search_params) { { state: :closed, labels: [label.title] } }
let(:mrs) { [merge_request_c] }
it_behaves_like 'searching with parameters'
end
end
......@@ -45,11 +45,32 @@ RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests
allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
end
let!(:merge_request1) { create(:merge_request, assignees: [user], author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') }
let!(:merge_request2) { create(:merge_request, :conflict, assignees: [user], author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, assignees: [user2], source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
let!(:merge_request1) do
create(:merge_request, assignees: [user], author: user,
source_project: project2, target_project: project1,
target_branch: 'merged-target')
end
let!(:merge_request2) do
create(:merge_request, :conflict, assignees: [user], author: user,
source_project: project2, target_project: project1,
state: 'closed')
end
let!(:merge_request3) do
create(:merge_request, :simple, author: user, assignees: [user2],
source_project: project2, target_project: project2,
state: 'locked',
title: 'thing WIP thing')
end
let!(:merge_request4) do
create(:merge_request, :simple, author: user,
source_project: project3, target_project: project3,
title: 'WIP thing')
end
let_it_be(:merge_request5) do
create(:merge_request, :simple, author: user,
source_project: project4, target_project: project4,
title: '[WIP]')
end
before do
project1.add_maintainer(user)
......
......@@ -22,7 +22,7 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
.with(full_path: parent.full_path)
.and_return(resolved_parent)
expect(resolver_class).to receive(:new)
expect(resolver_class.single).to receive(:new)
.with(object: resolved_parent, context: context, field: nil)
.and_call_original
......@@ -41,7 +41,7 @@ RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
it 'returns nil if issuable is not found' do
result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
result = type == :merge_request ? result.sync : result
result = result.respond_to?(:sync) ? result.sync : result
expect(result).to be_nil
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment