Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-15 03:06:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-15 03:06:05 +0300
commit386e5740f68fc7f49bc7692a28e927d6ea5ab056 (patch)
tree36c74aefd8101dbf439e11ba9bf1c0cf45f0ff16
parentb1ffdbb7f92407ceef575e557af07a3e3d067edf (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/graphql/mutations/merge_requests/set_assignees.rb48
-rw-r--r--app/graphql/types/mutation_operation_mode_enum.rb14
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/models/milestone.rb1
-rw-r--r--changelogs/unreleased/12769-milestone-blank-name-causes-issues.yml5
-rw-r--r--changelogs/unreleased/31919-graphql-MR-assignee-mutation.yml5
-rw-r--r--db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb18
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql71
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json204
-rw-r--r--doc/api/graphql/reference/index.md8
-rw-r--r--doc/ci/img/junit_test_report_ui.pngbin0 -> 84300 bytes
-rw-r--r--doc/ci/junit_test_reports.md24
-rw-r--r--spec/graphql/mutations/merge_requests/set_assignees_spec.rb106
-rw-r--r--spec/models/milestone_spec.rb11
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb134
16 files changed, 651 insertions, 1 deletions
diff --git a/app/graphql/mutations/merge_requests/set_assignees.rb b/app/graphql/mutations/merge_requests/set_assignees.rb
new file mode 100644
index 00000000000..8f0025f0a58
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/set_assignees.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class SetAssignees < Base
+ graphql_name 'MergeRequestSetAssignees'
+
+ argument :assignee_usernames,
+ [GraphQL::STRING_TYPE],
+ required: true,
+ description: <<~DESC
+ The usernames to assign to the merge request. Replaces existing assignees by default.
+ DESC
+
+ argument :operation_mode,
+ Types::MutationOperationModeEnum,
+ required: false,
+ description: <<~DESC
+ The operation to perform. Defaults to REPLACE.
+ DESC
+
+ def resolve(project_path:, iid:, assignee_usernames:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
+ Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/36098')
+
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ project = merge_request.project
+
+ assignee_ids = []
+ assignee_ids += merge_request.assignees.map(&:id) if Types::MutationOperationModeEnum.enum.values_at(:remove, :append).include?(operation_mode)
+ user_ids = UsersFinder.new(current_user, username: assignee_usernames).execute.map(&:id)
+
+ if operation_mode == Types::MutationOperationModeEnum.enum[:remove]
+ assignee_ids -= user_ids
+ else
+ assignee_ids |= user_ids
+ end
+
+ ::MergeRequests::UpdateService.new(project, current_user, assignee_ids: assignee_ids)
+ .execute(merge_request)
+
+ {
+ merge_request: merge_request,
+ errors: merge_request.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_operation_mode_enum.rb b/app/graphql/types/mutation_operation_mode_enum.rb
new file mode 100644
index 00000000000..90a29d2b0e5
--- /dev/null
+++ b/app/graphql/types/mutation_operation_mode_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class MutationOperationModeEnum < BaseEnum
+ graphql_name 'MutationOperationMode'
+ description 'Different toggles for changing mutator behavior.'
+
+ # Suggested param name for the enum: `operation_mode`
+
+ value 'REPLACE', 'Performs a replace operation'
+ value 'APPEND', 'Performs an append operation'
+ value 'REMOVE', 'Performs a removal operation'
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 632e1fdc2df..56e724292d5 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -11,6 +11,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetMilestone
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
+ mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 3552efbdd7b..d0be54eed02 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -60,6 +60,7 @@ class Milestone < ApplicationRecord
validates :group, presence: true, unless: :project
validates :project, presence: true, unless: :group
+ validates :title, presence: true
validate :uniqueness_of_title, if: :title_changed?
validate :milestone_type_check
diff --git a/changelogs/unreleased/12769-milestone-blank-name-causes-issues.yml b/changelogs/unreleased/12769-milestone-blank-name-causes-issues.yml
new file mode 100644
index 00000000000..44ec08e611b
--- /dev/null
+++ b/changelogs/unreleased/12769-milestone-blank-name-causes-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure milestone titles are never empty
+merge_request: 19985
+author:
+type: fixed
diff --git a/changelogs/unreleased/31919-graphql-MR-assignee-mutation.yml b/changelogs/unreleased/31919-graphql-MR-assignee-mutation.yml
new file mode 100644
index 00000000000..02c2e4421dc
--- /dev/null
+++ b/changelogs/unreleased/31919-graphql-MR-assignee-mutation.yml
@@ -0,0 +1,5 @@
+---
+title: Add MergeRequestSetAssignees GraphQL mutation
+merge_request: 19272
+author:
+type: added
diff --git a/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb b/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb
new file mode 100644
index 00000000000..76cb511424e
--- /dev/null
+++ b/db/migrate/20191112232338_ensure_no_empty_milestone_titles.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class EnsureNoEmptyMilestoneTitles < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ loop do
+ rows_updated = exec_update <<~SQL
+ UPDATE milestones SET title = '%BLANK' WHERE id IN (SELECT id FROM milestones WHERE title = '' LIMIT 500)
+ SQL
+ break if rows_updated < 500
+ end
+ end
+
+ def down; end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 36883130b78..72d1d957d6b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_11_12_221821) do
+ActiveRecord::Schema.define(version: 2019_11_12_232338) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index c75d9f236f9..2023f135550 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -3358,6 +3358,56 @@ type MergeRequestPermissions {
}
"""
+Autogenerated input type of MergeRequestSetAssignees
+"""
+input MergeRequestSetAssigneesInput {
+ """
+ The usernames to assign to the merge request. Replaces existing assignees by default.
+ """
+ assigneeUsernames: [String!]!
+
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The iid of the merge request to mutate
+ """
+ iid: String!
+
+ """
+ The operation to perform. Defaults to REPLACE.
+ """
+ operationMode: MutationOperationMode
+
+ """
+ The project the merge request to mutate is in
+ """
+ projectPath: ID!
+}
+
+"""
+Autogenerated return type of MergeRequestSetAssignees
+"""
+type MergeRequestSetAssigneesPayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Reasons why the mutation failed.
+ """
+ errors: [String!]!
+
+ """
+ The merge request after mutation
+ """
+ mergeRequest: MergeRequest
+}
+
+"""
Autogenerated input type of MergeRequestSetMilestone
"""
input MergeRequestSetMilestoneInput {
@@ -3537,6 +3587,7 @@ type Mutation {
destroyNote(input: DestroyNoteInput!): DestroyNotePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
+ mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload
mergeRequestSetMilestone(input: MergeRequestSetMilestoneInput!): MergeRequestSetMilestonePayload
mergeRequestSetWip(input: MergeRequestSetWipInput!): MergeRequestSetWipPayload
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
@@ -3546,6 +3597,26 @@ type Mutation {
updateNote(input: UpdateNoteInput!): UpdateNotePayload
}
+"""
+Different toggles for changing mutator behavior.
+"""
+enum MutationOperationMode {
+ """
+ Performs an append operation
+ """
+ APPEND
+
+ """
+ Performs a removal operation
+ """
+ REMOVE
+
+ """
+ Performs a replace operation
+ """
+ REPLACE
+}
+
type Namespace {
"""
Description of the namespace
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 629c18629cf..420b20919a7 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -14737,6 +14737,33 @@
"deprecationReason": null
},
{
+ "name": "mergeRequestSetAssignees",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestSetAssigneesInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequestSetAssigneesPayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "mergeRequestSetMilestone",
"description": null,
"args": [
@@ -15678,6 +15705,183 @@
},
{
"kind": "OBJECT",
+ "name": "MergeRequestSetAssigneesPayload",
+ "description": "Autogenerated return type of MergeRequestSetAssignees",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Reasons why the mutation failed.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "mergeRequest",
+ "description": "The merge request after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequest",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestSetAssigneesInput",
+ "description": "Autogenerated input type of MergeRequestSetAssignees",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "The project the merge request to mutate is in",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "iid",
+ "description": "The iid of the merge request to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "assigneeUsernames",
+ "description": "The usernames to assign to the merge request. Replaces existing assignees by default.\n",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "operationMode",
+ "description": "The operation to perform. Defaults to REPLACE.\n",
+ "type": {
+ "kind": "ENUM",
+ "name": "MutationOperationMode",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "ENUM",
+ "name": "MutationOperationMode",
+ "description": "Different toggles for changing mutator behavior.",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "REPLACE",
+ "description": "Performs a replace operation",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "APPEND",
+ "description": "Performs an append operation",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "REMOVE",
+ "description": "Performs a removal operation",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "CreateNotePayload",
"description": "Autogenerated return type of CreateNote",
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7ad698ee0b5..957ea489dac 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -483,6 +483,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `cherryPickOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `cherry_pick_on_current_merge_request` on this resource |
| `revertOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `revert_on_current_merge_request` on this resource |
+### MergeRequestSetAssigneesPayload
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `mergeRequest` | MergeRequest | The merge request after mutation |
+
### MergeRequestSetMilestonePayload
| Name | Type | Description |
diff --git a/doc/ci/img/junit_test_report_ui.png b/doc/ci/img/junit_test_report_ui.png
new file mode 100644
index 00000000000..380c6bbb89c
--- /dev/null
+++ b/doc/ci/img/junit_test_report_ui.png
Binary files differ
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index bc30e007393..3a94e9725e7 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -178,3 +178,27 @@ Currently, the following tools might not work because their XML formats are unsu
|Case|Tool|Issue|
|---|---|---|
|`<testcase>` does not have `classname` attribute|ESlint, sass-lint|<https://gitlab.com/gitlab-org/gitlab-foss/issues/50964>|
+
+## Viewing JUnit test reports on GitLab
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/24792) in GitLab 12.5.
+
+If JUnit XML files are generated and uploaded as part of a pipeline, these reports
+can be viewed inside the pipelines details page. The **Tests** tab on this page will
+display a list of test suites and cases reported from the XML file.
+
+![Test Reports Widget](img/junit_test_report_ui.png)
+
+You can view all the known test suites and click on each of these to see further
+details, including the cases that makeup the suite. Cases are ordered by status,
+with failed showing at the top, skipped next and successful cases last.
+
+### Enabling the feature
+
+This feature comes with the `:junit_pipeline_view` feature flag disabled by default.
+To enable this feature, ask a GitLab administrator with Rails console access to run the
+following command:
+
+```ruby
+Feature.enable(:junit_pipeline_view)
+```
diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
new file mode 100644
index 00000000000..e8da0e25b7d
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::MergeRequests::SetAssignees do
+ let(:merge_request) { create(:merge_request) }
+ let(:user) { create(:user) }
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) }
+
+ describe '#resolve' do
+ let(:assignee) { create(:user) }
+ let(:assignee2) { create(:user) }
+ let(:assignee_usernames) { [assignee.username] }
+ let(:mutated_merge_request) { subject[:merge_request] }
+ subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: assignee_usernames) }
+
+ before do
+ merge_request.project.add_developer(assignee)
+ merge_request.project.add_developer(assignee2)
+ end
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when the user can update the merge request' do
+ before do
+ merge_request.project.add_developer(user)
+ end
+
+ it 'replaces the assignee' do
+ merge_request.assignees = [assignee2]
+ merge_request.save!
+
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request.assignees).to contain_exactly(assignee)
+ expect(subject[:errors]).to be_empty
+ end
+
+ it 'returns errors merge request could not be updated' do
+ # Make the merge request invalid
+ merge_request.allow_broken = true
+ merge_request.update!(source_project: nil)
+
+ expect(subject[:errors]).not_to be_empty
+ end
+
+ context 'when passing an empty assignee list' do
+ let(:assignee_usernames) { [] }
+
+ before do
+ merge_request.assignees = [assignee]
+ merge_request.save!
+ end
+
+ it 'removes all assignees' do
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request.assignees).to eq([])
+ expect(subject[:errors]).to be_empty
+ end
+ end
+
+ context 'when passing "append" as true' do
+ subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: assignee_usernames, operation_mode: Types::MutationOperationModeEnum.enum[:append]) }
+
+ before do
+ merge_request.assignees = [assignee2]
+ merge_request.save!
+
+ # In CE, APPEND is a NOOP as you can't have multiple assignees
+ # We test multiple assignment in EE specs
+ stub_licensed_features(multiple_merge_request_assignees: false)
+ end
+
+ it 'is a NO-OP in FOSS' do
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request.assignees).to contain_exactly(assignee2)
+ expect(subject[:errors]).to be_empty
+ end
+ end
+
+ context 'when passing "remove" as true' do
+ before do
+ merge_request.assignees = [assignee]
+ merge_request.save!
+ end
+
+ it 'removes named assignee' do
+ mutated_merge_request = mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: assignee_usernames, operation_mode: Types::MutationOperationModeEnum.enum[:remove])[:merge_request]
+
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request.assignees).to eq([])
+ expect(subject[:errors]).to be_empty
+ end
+
+ it 'does not remove unnamed assignee' do
+ mutated_merge_request = mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: [assignee2.username], operation_mode: Types::MutationOperationModeEnum.enum[:remove])[:merge_request]
+
+ expect(mutated_merge_request).to eq(merge_request)
+ expect(mutated_merge_request.assignees).to contain_exactly(assignee)
+ expect(subject[:errors]).to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 120ba67f328..45cd2768708 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -55,6 +55,17 @@ describe Milestone do
end
end
+ describe 'title' do
+ it { is_expected.to validate_presence_of(:title) }
+
+ it 'is invalid if title would be empty after sanitation' do
+ milestone = build(:milestone, project: project, title: '<img src=x onerror=prompt(1)>')
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:title]).to include("can't be blank")
+ end
+ end
+
describe 'milestone_releases' do
let(:milestone) { build(:milestone, project: project) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
new file mode 100644
index 00000000000..8f908b7bf88
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Setting assignees of a merge request' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:assignee) { create(:user) }
+ let(:assignee2) { create(:user) }
+ let(:input) { { assignee_usernames: [assignee.username] } }
+ let(:expected_result) do
+ [{ 'username' => assignee.username }]
+ end
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: merge_request.iid.to_s
+ }
+ graphql_mutation(:merge_request_set_assignees, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ assignees {
+ nodes {
+ username
+ }
+ }
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:merge_request_set_assignees)
+ end
+
+ def mutation_assignee_nodes
+ mutation_response['mergeRequest']['assignees']['nodes']
+ end
+
+ before do
+ project.add_developer(current_user)
+ project.add_developer(assignee)
+ project.add_developer(assignee2)
+ end
+
+ it 'returns an error if the user is not allowed to update the merge request' do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ it 'does not allow members without the right permission to add assignees' do
+ user = create(:user)
+ project.add_guest(user)
+
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ context 'with assignees already assigned' do
+ before do
+ merge_request.assignees = [assignee2]
+ merge_request.save!
+ end
+
+ it 'replaces the assignee' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_assignee_nodes).to match_array(expected_result)
+ end
+ end
+
+ context 'when passing an empty list of assignees' do
+ let(:input) { { assignee_usernames: [] } }
+
+ before do
+ merge_request.assignees = [assignee2]
+ merge_request.save!
+ end
+
+ it 'removes assignee' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_assignee_nodes).to eq([])
+ end
+ end
+
+ context 'when passing append as true' do
+ let(:input) { { assignee_usernames: [assignee2.username], operation_mode: Types::MutationOperationModeEnum.enum[:append] } }
+
+ before do
+ # In CE, APPEND is a NOOP as you can't have multiple assignees
+ # We test multiple assignment in EE specs
+ stub_licensed_features(multiple_merge_request_assignees: false)
+
+ merge_request.assignees = [assignee]
+ merge_request.save!
+ end
+
+ it 'does not replace the assignee in CE' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_assignee_nodes).to match_array(expected_result)
+ end
+ end
+
+ context 'when passing remove as true' do
+ let(:input) { { assignee_usernames: [assignee.username], operation_mode: Types::MutationOperationModeEnum.enum[:remove] } }
+ let(:expected_result) { [] }
+
+ before do
+ merge_request.assignees = [assignee]
+ merge_request.save!
+ end
+
+ it 'removes the users in the list, while adding none' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_assignee_nodes).to match_array(expected_result)
+ end
+ end
+end