From 7cc4ab14b8a2f1d7d374a320b79374764527659f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 9 Aug 2016 19:26:45 +0200 Subject: [PATCH] New Notes::SlashCommandsService service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check for update_issuable permission in Notes::SlashCommandsService Signed-off-by: Rémy Coutable --- app/services/issuable_base_service.rb | 18 +- app/services/notes/create_service.rb | 43 +-- app/services/notes/slash_commands_service.rb | 22 ++ .../slash_commands/interpret_service.rb | 9 +- .../user_uses_slash_commands_spec.rb | 27 -- spec/services/notes/create_service_spec.rb | 23 +- .../notes/slash_commands_service_spec.rb | 129 +++++++++ .../slash_commands/interpret_service_spec.rb | 257 ++++++++++++++---- ...issuable_slash_commands_shared_examples.rb | 4 +- ..._service_slash_commands_shared_examples.rb | 116 -------- 10 files changed, 392 insertions(+), 256 deletions(-) create mode 100644 app/services/notes/slash_commands_service.rb create mode 100644 spec/services/notes/slash_commands_service_spec.rb delete mode 100644 spec/support/note_create_service_slash_commands_shared_examples.rb diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index b365e19c4a8..3512e2b735e 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -81,21 +81,21 @@ class IssuableBaseService < BaseService end def process_label_ids(attributes, base_label_ids: [], merge_all: false) - label_ids = attributes.delete(:label_ids) { [] } - add_label_ids = attributes.delete(:add_label_ids) { [] } - remove_label_ids = attributes.delete(:remove_label_ids) { [] } + label_ids = attributes.delete(:label_ids) + add_label_ids = attributes.delete(:add_label_ids) + remove_label_ids = attributes.delete(:remove_label_ids) new_label_ids = base_label_ids - new_label_ids |= label_ids if merge_all || (add_label_ids.empty? && remove_label_ids.empty?) - new_label_ids |= add_label_ids - new_label_ids -= remove_label_ids + new_label_ids = label_ids if label_ids && (merge_all || (add_label_ids.empty? && remove_label_ids.empty?)) + new_label_ids |= add_label_ids if add_label_ids + new_label_ids -= remove_label_ids if remove_label_ids new_label_ids end - def merge_slash_commands_into_params! + def merge_slash_commands_into_params!(issuable) command_params = SlashCommands::InterpretService.new(project, current_user). - execute(params[:description]) + execute(params[:description], issuable) params.merge!(command_params) end @@ -115,7 +115,7 @@ class IssuableBaseService < BaseService end def create(issuable) - merge_slash_commands_into_params! + merge_slash_commands_into_params!(issuable) filter_params if params.present? && create_issuable(issuable, params) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index d7531a5d63b..0c2513409a1 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -14,7 +14,7 @@ module Notes # We execute commands (extracted from `params[:note]`) on the noteable # **before** we save the note because if the note consists of commands # only, there is no need be create a note! - commands_executed = execute_slash_commands!(note) + commands_executed = SlashCommandsService.new(project, current_user).execute(note) if note.save # Finish the harder work in the background @@ -22,50 +22,13 @@ module Notes todo_service.new_note(note, current_user) end + # We must add the error after we call #save because errors are reset + # when #save is called if commands_executed && note.note.blank? note.errors.add(:commands_only, 'Your commands are being executed.') end note end - - private - - def execute_slash_commands!(note) - noteable_update_service = noteable_update_service(note.noteable_type) - return unless noteable_update_service - - command_params = SlashCommands::InterpretService.new(project, current_user). - execute(note.note) - - commands = execute_or_filter_commands(command_params, note) - - if commands.any? - noteable_update_service.new(project, current_user, commands).execute(note.noteable) - end - end - - def execute_or_filter_commands(commands, note) - final_commands = commands.reduce({}) do |memo, (command_key, command_value)| - if command_key != :due_date || note.noteable.respond_to?(:due_date) - memo[command_key] = command_value - end - - memo - end - - final_commands - end - - def noteable_update_service(noteable_type) - case noteable_type - when 'Issue' - Issues::UpdateService - when 'MergeRequest' - MergeRequests::UpdateService - else - nil - end - end end end diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb new file mode 100644 index 00000000000..54d43d06466 --- /dev/null +++ b/app/services/notes/slash_commands_service.rb @@ -0,0 +1,22 @@ +module Notes + class SlashCommandsService < BaseService + + UPDATE_SERVICES = { + 'Issue' => Issues::UpdateService, + 'MergeRequest' => MergeRequests::UpdateService + } + + def execute(note) + noteable_update_service = UPDATE_SERVICES[note.noteable_type] + return false unless noteable_update_service + return false unless can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable) + + commands = SlashCommands::InterpretService.new(project, current_user). + execute(note.note, note.noteable) + + if commands.any? + noteable_update_service.new(project, current_user, commands).execute(note.noteable) + end + end + end +end diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 2c92a4f7de5..bff61683976 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -2,9 +2,12 @@ module SlashCommands class InterpretService < BaseService include Gitlab::SlashCommands::Dsl + attr_reader :noteable + # Takes a text and interpret the commands that are extracted from it. # Returns a hash of changes to be applied to a record. - def execute(content) + def execute(content, noteable) + @noteable = noteable @updates = {} commands = extractor.extract_commands!(content) @@ -105,6 +108,8 @@ module SlashCommands desc 'Set a due date' params ' | ' command :due_date do |due_date_param| + return unless noteable.respond_to?(:due_date) + due_date = begin Time.now + ChronicDuration.parse(due_date_param) rescue ChronicDuration::DurationParseError @@ -116,6 +121,8 @@ module SlashCommands desc 'Remove due date' command :clear_due_date do + return unless noteable.respond_to?(:due_date) + @updates[:due_date] = nil end diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 890648f3860..08c452c6e59 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -29,31 +29,4 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do expect(page).not_to have_content '/due_date 2016-08-28' end end - - # Postponed because of high complexity - xdescribe 'merging from note' do - before do - project.team << [user, :master] - login_with(user) - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - end - - it 'creates a note without the commands and interpret the commands accordingly' do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Let's merge this!\n/merge\n/milestone %ASAP" - click_button 'Comment' - end - - expect(page).to have_content("Let's merge this!") - expect(page).not_to have_content('/merge') - expect(page).not_to have_content('/milestone %ASAP') - - merge_request.reload - note = merge_request.notes.user.first - - expect(note.note).to eq "Let's merge this!\r\n" - expect(merge_request).to be_merged - expect(merge_request.milestone).to eq milestone - end - end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 36ca7d2bce8..92dbccf0729 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -9,25 +9,30 @@ describe Notes::CreateService, services: true do end describe '#execute' do + before do + project.team << [user, :master] + end + context "valid params" do before do - project.team << [user, :master] @note = Notes::CreateService.new(project, user, opts).execute end it { expect(@note).to be_valid } it { expect(@note.note).to eq(opts[:note]) } + end - it_behaves_like 'note on noteable that supports slash commands' do - let(:noteable) { create(:issue, project: project) } - end + describe 'note with commands' do + describe '/close, /label, /assign & /milestone' do + let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) } - it_behaves_like 'note on noteable that supports slash commands' do - let(:noteable) { create(:merge_request, source_project: project) } - end + it 'saves the note and does not alter the note text' do + expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original - it_behaves_like 'note on noteable that does not support slash commands' do - let(:noteable) { create(:commit, project: project) } + note = described_class.new(project, user, opts.merge(note: note_text)).execute + + expect(note.note).to eq "HELLO\nWORLD" + end end end end diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/slash_commands_service_spec.rb new file mode 100644 index 00000000000..5632ec09834 --- /dev/null +++ b/spec/services/notes/slash_commands_service_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe Notes::SlashCommandsService, services: true do + shared_context 'note on noteable' do + let(:project) { create(:empty_project) } + let(:master) { create(:user).tap { |u| project.team << [u, :master] } } + let(:assignee) { create(:user) } + end + + shared_examples 'note on noteable that does not support slash commands' do + include_context 'note on noteable' + + before do + note.note = note_text + described_class.new(project, master).execute(note) + end + + describe 'note with only command' do + describe '/close, /label, /assign & /milestone' do + let(:note_text) { %(/close\n/assign @#{assignee.username}") } + + it 'saves the note and does not alter the note text' do + expect(note.note).to eq note_text + end + end + end + + describe 'note with command & text' do + describe '/close, /label, /assign & /milestone' do + let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) } + + it 'saves the note and does not alter the note text' do + expect(note.note).to eq note_text + end + end + end + end + + shared_examples 'note on noteable that supports slash commands' do + include_context 'note on noteable' + + before do + note.note = note_text + end + + let!(:milestone) { create(:milestone, project: project) } + let!(:labels) { create_pair(:label, project: project) } + + describe 'note with only command' do + describe '/close, /label, /assign & /milestone' do + let(:note_text) do + %(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}") + end + + it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do + described_class.new(project, master).execute(note) + + expect(note.note).to eq '' + expect(note.noteable).to be_closed + expect(note.noteable.labels).to match_array(labels) + expect(note.noteable.assignee).to eq(assignee) + expect(note.noteable.milestone).to eq(milestone) + end + end + + describe '/open' do + before do + note.noteable.close! + expect(note.noteable).to be_closed + end + let(:note_text) { '/open' } + + it 'opens the noteable, and leave no note' do + described_class.new(project, master).execute(note) + + expect(note.note).to eq '' + expect(note.noteable).to be_open + end + end + end + + describe 'note with command & text' do + describe '/close, /label, /assign & /milestone' do + let(:note_text) do + %(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}"\nWORLD) + end + + it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do + described_class.new(project, master).execute(note) + + expect(note.note).to eq "HELLO\nWORLD" + expect(note.noteable).to be_closed + expect(note.noteable.labels).to match_array(labels) + expect(note.noteable.assignee).to eq(assignee) + expect(note.noteable.milestone).to eq(milestone) + end + end + + describe '/open' do + before do + note.noteable.close + expect(note.noteable).to be_closed + end + let(:note_text) { "HELLO\n/open\nWORLD" } + + it 'opens the noteable' do + described_class.new(project, master).execute(note) + + expect(note.note).to eq "HELLO\nWORLD" + expect(note.noteable).to be_open + end + end + end + end + + describe '#execute' do + it_behaves_like 'note on noteable that supports slash commands' do + let(:note) { build(:note_on_issue, project: project) } + end + + it_behaves_like 'note on noteable that supports slash commands' do + let(:note) { build(:note_on_merge_request, project: project) } + end + + it_behaves_like 'note on noteable that does not support slash commands' do + let(:note) { build(:note_on_commit, project: project) } + end + end +end diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index fa0f65495ce..d03c84f59b3 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -18,8 +18,7 @@ describe SlashCommands::InterpretService, services: true do :assign, :reassign, :unassign, :remove_assignee, :milestone, - :remove_milestone, - :clear_milestone, + :clear_milestone, :remove_milestone, :labels, :label, :unlabel, :remove_labels, :remove_label, :clear_labels, :clear_label, @@ -35,10 +34,12 @@ describe SlashCommands::InterpretService, services: true do describe '#execute' do let(:service) { described_class.new(project, user) } + let(:issue) { create(:issue) } + let(:merge_request) { create(:merge_request) } shared_examples 'open command' do it 'returns state_event: "open" if content contains /open' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(state_event: 'reopen') end @@ -46,7 +47,7 @@ describe SlashCommands::InterpretService, services: true do shared_examples 'close command' do it 'returns state_event: "close" if content contains /open' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(state_event: 'close') end @@ -54,31 +55,47 @@ describe SlashCommands::InterpretService, services: true do shared_examples 'assign command' do it 'fetches assignee and populates assignee_id if content contains /assign' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(assignee_id: user.id) end end + shared_examples 'unassign command' do + it 'populates assignee_id: nil if content contains /unassign' do + changes = service.execute(content, issuable) + + expect(changes).to eq(assignee_id: nil) + end + end + shared_examples 'milestone command' do it 'fetches milestone and populates milestone_id if content contains /milestone' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(milestone_id: milestone.id) end end + shared_examples 'clear_milestone command' do + it 'populates milestone_id: nil if content contains /clear_milestone' do + changes = service.execute(content, issuable) + + expect(changes).to eq(milestone_id: nil) + end + end + shared_examples 'label command' do it 'fetches label ids and populates add_label_ids if content contains /label' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(add_label_ids: [bug.id, inprogress.id]) end end - shared_examples 'remove_labels command' do - it 'fetches label ids and populates remove_label_ids if content contains /label' do - changes = service.execute(content) + shared_examples 'unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do + changes = service.execute(content, issuable) expect(changes).to eq(remove_label_ids: [inprogress.id]) end @@ -86,15 +103,63 @@ describe SlashCommands::InterpretService, services: true do shared_examples 'clear_labels command' do it 'populates label_ids: [] if content contains /clear_labels' do - changes = service.execute(content) + changes = service.execute(content, issuable) expect(changes).to eq(label_ids: []) end end - shared_examples 'command returning no changes' do - it 'returns an empty hash if content contains /open' do - changes = service.execute(content) + shared_examples 'todo command' do + it 'populates todo_event: "mark" if content contains /todo' do + changes = service.execute(content, issuable) + + expect(changes).to eq(todo_event: 'mark') + end + end + + shared_examples 'done command' do + it 'populates todo_event: "done" if content contains /done' do + changes = service.execute(content, issuable) + + expect(changes).to eq(todo_event: 'done') + end + end + + shared_examples 'subscribe command' do + it 'populates subscription_event: "subscribe" if content contains /subscribe' do + changes = service.execute(content, issuable) + + expect(changes).to eq(subscription_event: 'subscribe') + end + end + + shared_examples 'unsubscribe command' do + it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do + changes = service.execute(content, issuable) + + expect(changes).to eq(subscription_event: 'unsubscribe') + end + end + + shared_examples 'due_date command' do + it 'populates due_date: Date.new(2016, 8, 28) if content contains /due_date 2016-08-28' do + changes = service.execute(content, issuable) + + expect(changes).to eq(due_date: Date.new(2016, 8, 28)) + end + end + + shared_examples 'clear_due_date command' do + it 'populates due_date: nil if content contains /clear_due_date' do + changes = service.execute(content, issuable) + + expect(changes).to eq(due_date: nil) + end + end + + shared_examples 'empty command' do + it 'populates {} if content contains an unsupported command' do + changes = service.execute(content, issuable) expect(changes).to be_empty end @@ -102,116 +167,204 @@ describe SlashCommands::InterpretService, services: true do it_behaves_like 'open command' do let(:content) { '/open' } + let(:issuable) { issue } + end + + it_behaves_like 'open command' do + let(:content) { '/open' } + let(:issuable) { merge_request } end it_behaves_like 'open command' do let(:content) { '/reopen' } + let(:issuable) { issue } end it_behaves_like 'close command' do let(:content) { '/close' } + let(:issuable) { issue } + end + + it_behaves_like 'close command' do + let(:content) { '/close' } + let(:issuable) { merge_request } end it_behaves_like 'assign command' do let(:content) { "/assign @#{user.username}" } + let(:issuable) { issue } + end + + it_behaves_like 'assign command' do + let(:content) { "/assign @#{user.username}" } + let(:issuable) { merge_request } end it 'does not populate assignee_id if content contains /assign with an unknown user' do - changes = service.execute('/assign joe') + changes = service.execute('/assign joe', issue) expect(changes).to be_empty end it 'does not populate assignee_id if content contains /assign without user' do - changes = service.execute('/assign') + changes = service.execute('/assign', issue) expect(changes).to be_empty end - it 'populates assignee_id: nil if content contains /unassign' do - changes = service.execute('/unassign') + it_behaves_like 'unassign command' do + let(:content) { '/unassign' } + let(:issuable) { issue } + end - expect(changes).to eq(assignee_id: nil) + it_behaves_like 'unassign command' do + let(:content) { '/unassign' } + let(:issuable) { merge_request } + end + + it_behaves_like 'unassign command' do + let(:content) { '/remove_assignee' } + let(:issuable) { issue } end it_behaves_like 'milestone command' do let(:content) { "/milestone %#{milestone.title}" } + let(:issuable) { issue } end - it 'populates milestone_id: nil if content contains /clear_milestone' do - changes = service.execute('/clear_milestone') + it_behaves_like 'milestone command' do + let(:content) { "/milestone %#{milestone.title}" } + let(:issuable) { merge_request } + end - expect(changes).to eq(milestone_id: nil) + it_behaves_like 'clear_milestone command' do + let(:content) { '/clear_milestone' } + let(:issuable) { issue } + end + + it_behaves_like 'clear_milestone command' do + let(:content) { '/clear_milestone' } + let(:issuable) { merge_request } + end + + it_behaves_like 'clear_milestone command' do + let(:content) { '/remove_milestone' } + let(:issuable) { issue } end it_behaves_like 'label command' do let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { issue } + end + + it_behaves_like 'label command' do + let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { merge_request } end it_behaves_like 'label command' do let(:content) { %(/labels ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { issue } end - it_behaves_like 'remove_labels command' do + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } + let(:issuable) { issue } end - it_behaves_like 'remove_labels command' do + it_behaves_like 'unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}") } + let(:issuable) { merge_request } + end + + it_behaves_like 'unlabel command' do let(:content) { %(/remove_labels ~"#{inprogress.title}") } + let(:issuable) { issue } end - it_behaves_like 'remove_labels command' do + it_behaves_like 'unlabel command' do let(:content) { %(/remove_label ~"#{inprogress.title}") } + let(:issuable) { issue } end it_behaves_like 'clear_labels command' do let(:content) { '/clear_labels' } + let(:issuable) { issue } + end + + it_behaves_like 'clear_labels command' do + let(:content) { '/clear_labels' } + let(:issuable) { merge_request } end it_behaves_like 'clear_labels command' do let(:content) { '/clear_label' } + let(:issuable) { issue } end - it 'populates todo: :mark if content contains /todo' do - changes = service.execute('/todo') - - expect(changes).to eq(todo_event: 'mark') + it_behaves_like 'todo command' do + let(:content) { '/todo' } + let(:issuable) { issue } end - it 'populates todo: :done if content contains /done' do - changes = service.execute('/done') - - expect(changes).to eq(todo_event: 'done') + it_behaves_like 'todo command' do + let(:content) { '/todo' } + let(:issuable) { merge_request } end - it 'populates subscription: :subscribe if content contains /subscribe' do - changes = service.execute('/subscribe') - - expect(changes).to eq(subscription_event: 'subscribe') + it_behaves_like 'done command' do + let(:content) { '/done' } + let(:issuable) { issue } end - it 'populates subscription: :unsubscribe if content contains /unsubscribe' do - changes = service.execute('/unsubscribe') - - expect(changes).to eq(subscription_event: 'unsubscribe') + it_behaves_like 'done command' do + let(:content) { '/done' } + let(:issuable) { merge_request } end - it 'populates due_date: Time.now.tomorrow if content contains /due_date 2016-08-28' do - changes = service.execute('/due_date 2016-08-28') - - expect(changes).to eq(due_date: Date.new(2016, 8, 28)) + it_behaves_like 'subscribe command' do + let(:content) { '/subscribe' } + let(:issuable) { issue } end - it 'populates due_date: Time.now.tomorrow if content contains /due_date foo' do - changes = service.execute('/due_date foo') - - expect(changes).to be_empty + it_behaves_like 'subscribe command' do + let(:content) { '/subscribe' } + let(:issuable) { merge_request } end - it 'populates due_date: nil if content contains /clear_due_date' do - changes = service.execute('/clear_due_date') + it_behaves_like 'unsubscribe command' do + let(:content) { '/unsubscribe' } + let(:issuable) { issue } + end - expect(changes).to eq(due_date: nil) + it_behaves_like 'unsubscribe command' do + let(:content) { '/unsubscribe' } + let(:issuable) { merge_request } + end + + it_behaves_like 'due_date command' do + let(:content) { '/due_date 2016-08-28' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/due_date foo bar' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/due_date 2016-08-28' } + let(:issuable) { merge_request } + end + + it_behaves_like 'clear_due_date command' do + let(:content) { '/clear_due_date' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/clear_due_date' } + let(:issuable) { merge_request } end end end diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb index 3b8783cb053..824ccb0ddfb 100644 --- a/spec/support/issuable_slash_commands_shared_examples.rb +++ b/spec/support/issuable_slash_commands_shared_examples.rb @@ -118,7 +118,7 @@ shared_examples 'issuable record that supports slash commands in its description end expect(page).not_to have_content '/close' - expect(page).to have_content 'Your commands are being executed.' + expect(page).not_to have_content 'Your commands are being executed.' expect(issuable).to be_open end @@ -159,7 +159,7 @@ shared_examples 'issuable record that supports slash commands in its description end expect(page).not_to have_content '/reopen' - expect(page).to have_content 'Your commands are being executed.' + expect(page).not_to have_content 'Your commands are being executed.' expect(issuable).to be_closed end diff --git a/spec/support/note_create_service_slash_commands_shared_examples.rb b/spec/support/note_create_service_slash_commands_shared_examples.rb deleted file mode 100644 index 3f7ad8b2f91..00000000000 --- a/spec/support/note_create_service_slash_commands_shared_examples.rb +++ /dev/null @@ -1,116 +0,0 @@ -# Specifications for behavior common to all note objects with executable attributes. -# It expects a `noteable` object for which the note is posted. - -shared_context 'note on noteable' do - let!(:project) { create(:project) } - let(:user) { create(:user).tap { |u| project.team << [u, :master] } } - let(:assignee) { create(:user) } - let(:base_params) { { noteable: noteable } } - let(:params) { base_params.merge(example_params) } - let(:note) { described_class.new(project, user, params).execute } -end - -shared_examples 'note on noteable that does not support slash commands' do - include_context 'note on noteable' - - let(:params) { { commit_id: noteable.id, noteable_type: 'Commit' }.merge(example_params) } - - describe 'note with only command' do - describe '/close, /label, /assign & /milestone' do - let(:note_text) { %(/close\n/assign @#{assignee.username}") } - let(:example_params) { { note: note_text } } - - it 'saves the note and does not alter the note text' do - expect(note).to be_persisted - expect(note.note).to eq note_text - end - end - end - - describe 'note with command & text' do - describe '/close, /label, /assign & /milestone' do - let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) } - let(:example_params) { { note: note_text } } - - it 'saves the note and does not alter the note text' do - expect(note).to be_persisted - expect(note.note).to eq note_text - end - end - end -end - -shared_examples 'note on noteable that supports slash commands' do - include_context 'note on noteable' - - let!(:milestone) { create(:milestone, project: project) } - let!(:labels) { create_pair(:label, project: project) } - - describe 'note with only command' do - describe '/close, /label, /assign & /milestone' do - let(:example_params) do - { - note: %(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}") - } - end - - it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do - expect(note).not_to be_persisted - expect(note.note).to eq '' - expect(noteable).to be_closed - expect(noteable.labels).to match_array(labels) - expect(noteable.assignee).to eq(assignee) - expect(noteable.milestone).to eq(milestone) - end - end - - describe '/open' do - let(:noteable) { create(:issue, project: project, state: :closed) } - let(:example_params) do - { - note: '/open' - } - end - - it 'opens the noteable, and leave no note' do - expect(note).not_to be_persisted - expect(note.note).to eq '' - expect(noteable).to be_open - end - end - end - - describe 'note with command & text' do - describe '/close, /label, /assign & /milestone' do - let(:example_params) do - { - note: %(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n/milestone %"#{milestone.name}"\nWORLD) - } - end - - it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do - expect(note).to be_persisted - expect(note.note).to eq "HELLO\nWORLD" - expect(noteable).to be_closed - expect(noteable.labels).to match_array(labels) - expect(noteable.assignee).to eq(assignee) - expect(noteable.milestone).to eq(milestone) - end - end - - describe '/open' do - let(:noteable) { create(:issue, project: project, state: :closed) } - let(:example_params) do - { - note: "HELLO\n/open\nWORLD" - } - end - - it 'opens the noteable' do - expect(note).to be_persisted - expect(note.note).to eq "HELLO\nWORLD" - expect(noteable).to be_open - end - end - end -end