Commit 37b08fe6 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '229838-create-issue-graphql' into 'master'

Add graphql mutation to create an issue

See merge request gitlab-org/gitlab!43735
parents 7677855b ebfec71c
# frozen_string_literal: true
module Mutations
module Issues
module CommonMutationArguments
extend ActiveSupport::Concern
included do
argument :description, GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :description)
argument :due_date, GraphQL::Types::ISO8601Date,
required: false,
description: copy_field_description(Types::IssueType, :due_date)
argument :confidential, GraphQL::BOOLEAN_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :confidential)
argument :locked, GraphQL::BOOLEAN_TYPE,
as: :discussion_locked,
required: false,
description: copy_field_description(Types::IssueType, :discussion_locked)
end
end
end
end
Mutations::Issues::CommonMutationArguments.prepend_if_ee('::EE::Mutations::Issues::CommonMutationArguments')
# frozen_string_literal: true
module Mutations
module Issues
class Create < BaseMutation
include ResolvesProject
graphql_name 'CreateIssue'
authorize :create_issue
include CommonMutationArguments
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'Project full path the issue is associated with'
argument :iid, GraphQL::INT_TYPE,
required: false,
description: 'The IID (internal ID) of a project issue. Only admins and project owners can modify'
argument :title, GraphQL::STRING_TYPE,
required: true,
description: copy_field_description(Types::IssueType, :title)
argument :milestone_id, ::Types::GlobalIDType[::Milestone],
required: false,
description: 'The ID of the milestone to assign to the issue. On update milestone will be removed if set to null'
argument :labels, [GraphQL::STRING_TYPE],
required: false,
description: copy_field_description(Types::IssueType, :labels)
argument :label_ids, [::Types::GlobalIDType[::Label]],
required: false,
description: 'The IDs of labels to be added to the issue'
argument :created_at, Types::TimeType,
required: false,
description: 'Timestamp when the issue was created. Available only for admins and project owners'
argument :merge_request_to_resolve_discussions_of, ::Types::GlobalIDType[::MergeRequest],
required: false,
description: 'The IID of a merge request for which to resolve discussions'
argument :discussion_to_resolve, GraphQL::STRING_TYPE,
required: false,
description: 'The ID of a discussion to resolve. Also pass `merge_request_to_resolve_discussions_of`'
argument :assignee_ids, [::Types::GlobalIDType[::User]],
required: false,
description: 'The array of user IDs to assign to the issue'
field :issue,
Types::IssueType,
null: true,
description: 'The issue after mutation'
def ready?(**args)
if args.slice(*mutually_exclusive_label_args).size > 1
arg_str = mutually_exclusive_label_args.map { |x| x.to_s.camelize(:lower) }.join(' or ')
raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required."
end
if args[:discussion_to_resolve].present? && args[:merge_request_to_resolve_discussions_of].blank?
raise Gitlab::Graphql::Errors::ArgumentError,
'to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter'
end
super
end
def resolve(project_path:, **attributes)
project = authorized_find!(full_path: project_path)
params = build_create_issue_params(attributes.merge(author_id: current_user.id))
issue = ::Issues::CreateService.new(project, current_user, params).execute
if issue.spam?
issue.errors.add(:base, 'Spam detected.')
end
{
issue: issue.valid? ? issue : nil,
errors: errors_on_object(issue)
}
end
private
def build_create_issue_params(params)
params[:milestone_id] &&= params[:milestone_id]&.model_id
params[:assignee_ids] &&= params[:assignee_ids].map { |assignee_id| assignee_id&.model_id }
params[:label_ids] &&= params[:label_ids].map { |label_id| label_id&.model_id }
params
end
def mutually_exclusive_label_args
[:labels, :label_ids]
end
def find_object(full_path:)
resolve_project(full_path: full_path)
end
end
end
end
Mutations::Issues::Create.prepend_if_ee('::EE::Mutations::Issues::Create')
...@@ -5,49 +5,26 @@ module Mutations ...@@ -5,49 +5,26 @@ module Mutations
class Update < Base class Update < Base
graphql_name 'UpdateIssue' graphql_name 'UpdateIssue'
argument :title, include CommonMutationArguments
GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :title)
argument :description, argument :title, GraphQL::STRING_TYPE,
GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :description)
argument :due_date,
Types::TimeType,
required: false,
description: copy_field_description(Types::IssueType, :due_date)
argument :confidential,
GraphQL::BOOLEAN_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :confidential)
argument :locked,
GraphQL::BOOLEAN_TYPE,
as: :discussion_locked,
required: false, required: false,
description: copy_field_description(Types::IssueType, :discussion_locked) description: copy_field_description(Types::IssueType, :title)
argument :add_label_ids, argument :milestone_id, GraphQL::ID_TYPE,
[GraphQL::ID_TYPE],
required: false, required: false,
description: 'The IDs of labels to be added to the issue.' description: 'The ID of the milestone to assign to the issue. On update milestone will be removed if set to null'
argument :remove_label_ids, argument :add_label_ids, [GraphQL::ID_TYPE],
[GraphQL::ID_TYPE],
required: false, required: false,
description: 'The IDs of labels to be removed from the issue.' description: 'The IDs of labels to be added to the issue'
argument :milestone_id, argument :remove_label_ids, [GraphQL::ID_TYPE],
GraphQL::ID_TYPE,
required: false, required: false,
description: 'The ID of the milestone to be assigned, milestone will be removed if set to null.' description: 'The IDs of labels to be removed from the issue'
argument :state_event, Types::IssueStateEventEnum, argument :state_event, Types::IssueStateEventEnum,
description: 'Close or reopen an issue.', description: 'Close or reopen an issue',
required: false required: false
def resolve(project_path:, iid:, **args) def resolve(project_path:, iid:, **args)
......
...@@ -23,6 +23,7 @@ module Types ...@@ -23,6 +23,7 @@ module Types
mount_mutation Mutations::Branches::Create, calls_gitaly: true mount_mutation Mutations::Branches::Create, calls_gitaly: true
mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::Discussions::ToggleResolve mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::Create
mount_mutation Mutations::Issues::SetAssignees mount_mutation Mutations::Issues::SetAssignees
mount_mutation Mutations::Issues::SetConfidential mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetLocked
......
...@@ -34,6 +34,18 @@ module Issues ...@@ -34,6 +34,18 @@ module Issues
private private
def filter_params(merge_request)
super
moved_issue = params.delete(:moved_issue)
# Setting created_at, updated_at and iid is allowed only for admins and owners or
# when moving an issue as we preserve the original issue attributes except id and iid.
params.delete(:iid) unless current_user.can?(:set_issue_iid, project)
params.delete(:created_at) unless moved_issue || current_user.can?(:set_issue_created_at, project)
params.delete(:updated_at) unless moved_issue || current_user.can?(:set_issue_updated_at, project)
end
def create_assignee_note(issue, old_assignees) def create_assignee_note(issue, old_assignees)
SystemNoteService.change_issuable_assignees( SystemNoteService.change_issuable_assignees(
issue, issue.project, current_user, old_assignees) issue, issue.project, current_user, old_assignees)
......
...@@ -52,7 +52,8 @@ module Issues ...@@ -52,7 +52,8 @@ module Issues
iid: nil, iid: nil,
project: target_project, project: target_project,
author: original_entity.author, author: original_entity.author,
assignee_ids: original_entity.assignee_ids assignee_ids: original_entity.assignee_ids,
moved_issue: true
} }
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params) new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
......
---
title: Add GraphQL mutation to create an issue
merge_request: 43735
author:
type: added
...@@ -3422,6 +3422,111 @@ type CreateImageDiffNotePayload { ...@@ -3422,6 +3422,111 @@ type CreateImageDiffNotePayload {
note: Note note: Note
} }
"""
Autogenerated input type of CreateIssue
"""
input CreateIssueInput {
"""
The array of user IDs to assign to the issue
"""
assigneeIds: [UserID!]
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Indicates the issue is confidential
"""
confidential: Boolean
"""
Timestamp when the issue was created. Available only for admins and project owners
"""
createdAt: Time
"""
Description of the issue
"""
description: String
"""
The ID of a discussion to resolve. Also pass `merge_request_to_resolve_discussions_of`
"""
discussionToResolve: String
"""
Due date of the issue
"""
dueDate: ISO8601Date
"""
The ID of an epic to associate the issue with
"""
epicId: EpicID
"""
The IID (internal ID) of a project issue. Only admins and project owners can modify
"""
iid: Int
"""
The IDs of labels to be added to the issue
"""
labelIds: [LabelID!]
"""
Labels of the issue
"""
labels: [String!]
"""
Indicates discussion is locked on the issue
"""
locked: Boolean
"""
The IID of a merge request for which to resolve discussions
"""
mergeRequestToResolveDiscussionsOf: MergeRequestID
"""
The ID of the milestone to assign to the issue. On update milestone will be removed if set to null
"""
milestoneId: MilestoneID
"""
Project full path the issue is associated with
"""
projectPath: ID!
"""
Title of the issue
"""
title: String!
}
"""
Autogenerated return type of CreateIssue
"""
type CreateIssuePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue after mutation
"""
issue: Issue
}
""" """
Autogenerated input type of CreateIteration Autogenerated input type of CreateIteration
""" """
...@@ -11188,6 +11293,11 @@ type MergeRequestEdge { ...@@ -11188,6 +11293,11 @@ type MergeRequestEdge {
node: MergeRequest node: MergeRequest
} }
"""
Identifier of MergeRequest
"""
scalar MergeRequestID
""" """
Check permissions for the current user on a merge request Check permissions for the current user on a merge request
""" """
...@@ -11964,6 +12074,7 @@ type Mutation { ...@@ -11964,6 +12074,7 @@ type Mutation {
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload
createEpic(input: CreateEpicInput!): CreateEpicPayload createEpic(input: CreateEpicInput!): CreateEpicPayload
createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload
createIssue(input: CreateIssueInput!): CreateIssuePayload
createIteration(input: CreateIterationInput!): CreateIterationPayload createIteration(input: CreateIterationInput!): CreateIterationPayload
createNote(input: CreateNoteInput!): CreateNotePayload createNote(input: CreateNoteInput!): CreateNotePayload
createRequirement(input: CreateRequirementInput!): CreateRequirementPayload createRequirement(input: CreateRequirementInput!): CreateRequirementPayload
...@@ -19452,7 +19563,7 @@ Autogenerated input type of UpdateIssue ...@@ -19452,7 +19563,7 @@ Autogenerated input type of UpdateIssue
""" """
input UpdateIssueInput { input UpdateIssueInput {
""" """
The IDs of labels to be added to the issue. The IDs of labels to be added to the issue
""" """
addLabelIds: [ID!] addLabelIds: [ID!]
...@@ -19474,18 +19585,13 @@ input UpdateIssueInput { ...@@ -19474,18 +19585,13 @@ input UpdateIssueInput {
""" """
Due date of the issue Due date of the issue
""" """
dueDate: Time dueDate: ISO8601Date
""" """
The ID of the parent epic. NULL when removing the association The ID of the parent epic. NULL when removing the association
""" """
epicId: ID epicId: ID
"""
The desired health status
"""
healthStatus: HealthStatus
""" """
The IID of the issue to mutate The IID of the issue to mutate
""" """
...@@ -19497,7 +19603,7 @@ input UpdateIssueInput { ...@@ -19497,7 +19603,7 @@ input UpdateIssueInput {
locked: Boolean locked: Boolean
""" """
The ID of the milestone to be assigned, milestone will be removed if set to null. The ID of the milestone to assign to the issue. On update milestone will be removed if set to null
""" """
milestoneId: ID milestoneId: ID
...@@ -19507,12 +19613,12 @@ input UpdateIssueInput { ...@@ -19507,12 +19613,12 @@ input UpdateIssueInput {
projectPath: ID! projectPath: ID!
""" """
The IDs of labels to be removed from the issue. The IDs of labels to be removed from the issue
""" """
removeLabelIds: [ID!] removeLabelIds: [ID!]
""" """
Close or reopen an issue. Close or reopen an issue
""" """
stateEvent: IssueStateEvent stateEvent: IssueStateEvent
......
...@@ -537,6 +537,16 @@ Autogenerated return type of CreateImageDiffNote. ...@@ -537,6 +537,16 @@ Autogenerated return type of CreateImageDiffNote.
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `note` | Note | The note after mutation | | `note` | Note | The note after mutation |
### CreateIssuePayload
Autogenerated return type of CreateIssue.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
### CreateIterationPayload ### CreateIterationPayload
Autogenerated return type of CreateIteration. Autogenerated return type of CreateIteration.
......
# frozen_string_literal: true
module EE
module Mutations
module Issues
module CommonMutationArguments
extend ActiveSupport::Concern
included do
argument :health_status,
::Types::HealthStatusEnum,
required: false,
description: 'The desired health status'
argument :weight, GraphQL::INT_TYPE,
required: false,
description: 'The weight of the issue'
end
end
end
end
end
# frozen_string_literal: true
module EE
module Mutations
module Issues
module Create
extend ActiveSupport::Concern
prepended do
argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false,
description: 'The ID of an epic to associate the issue with'
end
private
def create_issue_params(params)
params[:epic_id] &&= params[:epic_id]&.model_id
super(params)
end
end
end
end
end
...@@ -7,12 +7,7 @@ module EE ...@@ -7,12 +7,7 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
argument :health_status, argument :epic_id, GraphQL::ID_TYPE,
::Types::HealthStatusEnum,
required: false,
description: 'The desired health status'
argument :epic_id,
GraphQL::ID_TYPE,
required: false, required: false,
description: 'The ID of the parent epic. NULL when removing the association' description: 'The ID of the parent epic. NULL when removing the association'
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let(:expected_attributes) do
{
title: 'new title',
description: 'new description',
confidential: true,
due_date: Date.tomorrow,
discussion_locked: true,
weight: 10
}
end
let(:mutation_params) do
{
project_path: project.full_path,
assignee_ids: [assignee1.to_global_id, assignee2.to_global_id],
health_status: Issue.health_statuses[:at_risk]
}.merge(expected_attributes)
end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_issue) { subject[:issue] }
specify { expect(described_class).to require_graphql_authorizations(:create_issue) }
describe '#resolve' do
before do
project.add_guest(assignee1)
project.add_guest(assignee2)
stub_licensed_features(issuable_health_status: true)
end
subject { mutation.resolve(mutation_params) }
context 'when user can create issues' do
before do
project.add_developer(user)
end
it 'creates issue with correct EE values' do
expect(mutated_issue).to have_attributes(expected_attributes)
expect(mutated_issue.assignees.pluck(:id)).to eq([assignee1.id, assignee2.id])
expect(mutated_issue.health_status).to eq('at_risk')
end
end
end
end
...@@ -16,7 +16,15 @@ RSpec.describe Mutations::Issues::Update do ...@@ -16,7 +16,15 @@ RSpec.describe Mutations::Issues::Update do
let_it_be(:epic) { create(:epic, group: group) } let_it_be(:epic) { create(:epic, group: group) }
let(:epic_id) { epic.to_global_id.to_s } let(:epic_id) { epic.to_global_id.to_s }
let(:params) { { project_path: project.full_path, iid: issue.iid, epic_id: epic_id } } let(:params) do
{
project_path: project.full_path,
iid: issue.iid,
epic_id: epic_id,
weight: 10
}
end
let(:mutated_issue) { subject[:issue] } let(:mutated_issue) { subject[:issue] }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
...@@ -53,6 +61,7 @@ RSpec.describe Mutations::Issues::Update do ...@@ -53,6 +61,7 @@ RSpec.describe Mutations::Issues::Update do
it 'returns the updated issue' do it 'returns the updated issue' do
expect(mutated_issue.epic).to eq(epic) expect(mutated_issue.epic).to eq(epic)
expect(mutated_issue.weight).to eq(10)
end end
end end
......
...@@ -231,9 +231,6 @@ module API ...@@ -231,9 +231,6 @@ module API
authorize! :create_issue, user_project authorize! :create_issue, user_project
params.delete(:created_at) unless current_user.can?(:set_issue_created_at, user_project)
params.delete(:iid) unless current_user.can?(:set_issue_iid, user_project)
issue_params = declared_params(include_missing: false) issue_params = declared_params(include_missing: false)
issue_params[:system_note_timestamp] = params[:created_at] issue_params[:system_note_timestamp] = params[:created_at]
...@@ -279,8 +276,6 @@ module API ...@@ -279,8 +276,6 @@ module API
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid)) issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue authorize! :update_issue, issue
# Setting updated_at is allowed only for admins and owners
params.delete(:updated_at) unless current_user.can?(:set_issue_updated_at, user_project)
issue.system_note_timestamp = params[:updated_at] issue.system_note_timestamp = params[:updated_at]
update_params = declared_params(include_missing: false).merge(request: request, api: true) update_params = declared_params(include_missing: false).merge(request: request, api: true)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let_it_be(:project_label1) { create(:label, project: project) }
let_it_be(:project_label2) { create(:label, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:new_label1) { FFaker::Lorem.word }
let_it_be(:new_label2) { new_label1 + 'Extra' }
let(:expected_attributes) do
{
title: 'new title',
description: 'new description',
confidential: true,
due_date: Date.tomorrow,
discussion_locked: true
}
end
let(:mutation_params) do
{
project_path: project.full_path,
milestone_id: milestone.to_global_id,
labels: [project_label1.title, project_label2.title, new_label1, new_label2],
assignee_ids: [assignee1.to_global_id, assignee2.to_global_id]
}.merge(expected_attributes)
end
let(:special_params) do
{
iid: non_existing_record_id,
created_at: 2.days.ago
}
end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_issue) { subject[:issue] }
specify { expect(described_class).to require_graphql_authorizations(:create_issue) }
describe '#resolve' do
before do
stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
project.add_guest(assignee1)
project.add_guest(assignee2)
end
subject { mutation.resolve(mutation_params) }
context 'when the user does not have permission to create an issue' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can create an issue' do
context 'when creating an issue a developer' do
before do
project.add_developer(user)
end
it 'creates issue with correct values' do
expect(mutated_issue).to have_attributes(expected_attributes)
expect(mutated_issue.milestone_id).to eq(milestone.id)
expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title, new_label1, new_label2])
expect(mutated_issue.assignees.pluck(:id)).to eq([assignee1.id])
end
context 'when passing in label_ids' do
before do
mutation_params.delete(:labels)
mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
end
it 'creates issue with correct values' do
expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title])
end
end
context 'when trying to create issue with restricted params' do
before do
mutation_params.merge!(special_params)
end
it 'ignores the special params' do
expect(mutated_issue).not_to be_like_time(special_params[:created_at])
expect(mutated_issue.iid).not_to eq(special_params[:iid])
end
end
end
context 'when creating an issue as owner' do
let_it_be(:user) { project.owner }
before do
mutation_params.merge!(special_params)
end
it 'sets the special params' do
expect(mutated_issue.created_at).to be_like_time(special_params[:created_at])
expect(mutated_issue.iid).to eq(special_params[:iid])
end
end
end
end
describe "#ready?" do
context 'when passing in both labels and label_ids' do
before do
mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
context 'when passing only `discussion_to_resolve` param' do
before do
mutation_params.merge!(discussion_to_resolve: 'abc')
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/)
end
end
context 'when passing only `merge_request_to_resolve_discussions_of` param' do
before do
mutation_params.merge!(merge_request_to_resolve_discussions_of: 'abc')
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }.not_to raise_error
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create an issue' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let_it_be(:project_label1) { create(:label, project: project) }
let_it_be(:project_label2) { create(:label, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:new_label1) { FFaker::Lorem.word }
let_it_be(:new_label2) { FFaker::Lorem.word }
let(:input) do
{
'title' => 'new title',
'description' => 'new description',
'confidential' => true,
'dueDate' => Date.tomorrow.strftime('%Y-%m-%d')
}
end
let(:mutation) { graphql_mutation(:createIssue, input.merge('projectPath' => project.full_path, 'locked' => true)) }
let(:mutation_response) { graphql_mutation_response(:create_issue) }
context 'the user is not allowed to create an issue' do
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create an issue' do
before do
project.add_developer(current_user)
end
it 'updates the issue' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['issue']).to include(input)
expect(mutation_response['issue']).to include('discussionLocked' => true)
end
end
end
...@@ -10,13 +10,15 @@ RSpec.describe 'Update of an existing issue' do ...@@ -10,13 +10,15 @@ RSpec.describe 'Update of an existing issue' do
let_it_be(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let(:input) do let(:input) do
{ {
project_path: project.full_path, 'iid' => issue.iid.to_s,
iid: issue.iid.to_s, 'title' => 'new title',
locked: true 'description' => 'new description',
'confidential' => true,
'dueDate' => Date.tomorrow.strftime('%Y-%m-%d')
} }
end end
let(:mutation) { graphql_mutation(:update_issue, input) } let(:mutation) { graphql_mutation(:update_issue, input.merge(project_path: project.full_path, locked: true)) }
let(:mutation_response) { graphql_mutation_response(:update_issue) } let(:mutation_response) { graphql_mutation_response(:update_issue) }
context 'the user is not allowed to update issue' do context 'the user is not allowed to update issue' do
...@@ -32,9 +34,8 @@ RSpec.describe 'Update of an existing issue' do ...@@ -32,9 +34,8 @@ RSpec.describe 'Update of an existing issue' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['issue']).to include( expect(mutation_response['issue']).to include(input)
'discussionLocked' => true expect(mutation_response['issue']).to include('discussionLocked' => true)
)
end end
end end
end 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