Commit 029b7d2e authored by Douwe Maan's avatar Douwe Maan

Fixed specs and fixes based on failing specs

parent b2b1b4a4
...@@ -176,12 +176,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -176,12 +176,7 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def issue def issue
@noteable = @issue ||= @noteable = @issue ||= @project.issues.find_by(iid: params[:id]) || redirect_old
begin
@project.issues.find_by!(iid: params[:id])
rescue ActiveRecord::RecordNotFound
redirect_old
end
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue alias_method :issuable, :issue
...@@ -225,7 +220,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -225,7 +220,6 @@ class Projects::IssuesController < Projects::ApplicationController
if issue if issue
redirect_to issue_path(issue) redirect_to issue_path(issue)
return
else else
raise ActiveRecord::RecordNotFound.new raise ActiveRecord::RecordNotFound.new
end end
......
...@@ -15,20 +15,29 @@ module Notes ...@@ -15,20 +15,29 @@ module Notes
# **before** we save the note because if the note consists of commands # **before** we save the note because if the note consists of commands
# only, there is no need be create a note! # only, there is no need be create a note!
slash_commands_service = SlashCommandsService.new(project, current_user) slash_commands_service = SlashCommandsService.new(project, current_user)
content, command_params = slash_commands_service.extract_commands(note)
note.note = content if slash_commands_service.supported?(note)
content, command_params = slash_commands_service.extract_commands(note)
if note.save only_commands = content.empty?
note.note = content
end
if !only_commands && note.save
# Finish the harder work in the background # Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params) NewNoteWorker.perform_in(2.seconds, note.id, params)
todo_service.new_note(note, current_user) todo_service.new_note(note, current_user)
end end
# We must add the error after we call #save because errors are reset if command_params && command_params.any?
# when #save is called slash_commands_service.execute(command_params, note)
if slash_commands_service.execute(command_params, note) && note.note.blank?
note.errors.add(:commands_only, 'Your commands have been executed!') # We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
note.errors.add(:commands_only, 'Your commands have been executed!')
end
end end
note note
......
...@@ -5,10 +5,13 @@ module Notes ...@@ -5,10 +5,13 @@ module Notes
'MergeRequest' => MergeRequests::UpdateService 'MergeRequest' => MergeRequests::UpdateService
} }
def supported?(note)
noteable_update_service(note) &&
can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
end
def extract_commands(note) def extract_commands(note)
@noteable_update_service = UPDATE_SERVICES[note.noteable_type] return [note.note, {}] unless supported?(note)
return [] unless @noteable_update_service
return [] unless can?(current_user, :"update_#{note.noteable_type.underscore}", note.noteable)
SlashCommands::InterpretService.new(project, current_user). SlashCommands::InterpretService.new(project, current_user).
execute(note.note, note.noteable) execute(note.note, note.noteable)
...@@ -16,9 +19,15 @@ module Notes ...@@ -16,9 +19,15 @@ module Notes
def execute(command_params, note) def execute(command_params, note)
return if command_params.empty? return if command_params.empty?
return unless supported?(note)
noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
end
private
@noteable_update_service.new(project, current_user, command_params). def noteable_update_service(note)
execute(note.noteable) UPDATE_SERVICES[note.noteable_type]
end end
end end
end end
...@@ -2,16 +2,16 @@ module SlashCommands ...@@ -2,16 +2,16 @@ module SlashCommands
class InterpretService < BaseService class InterpretService < BaseService
include Gitlab::SlashCommands::Dsl include Gitlab::SlashCommands::Dsl
attr_reader :noteable attr_reader :issuable
# Takes a text and interpret the commands that are extracted from it. # Takes a text and interpret the commands that are extracted from it.
# Returns a hash of changes to be applied to a record. # Returns a hash of changes to be applied to a record.
def execute(content, noteable) def execute(content, issuable)
@noteable = noteable @issuable = issuable
@updates = {} @updates = {}
opts = { opts = {
noteable: noteable, issuable: issuable,
current_user: current_user, current_user: current_user,
project: project project: project
} }
...@@ -35,23 +35,24 @@ module SlashCommands ...@@ -35,23 +35,24 @@ module SlashCommands
end end
desc do desc do
"Close this #{noteable.to_ability_name.humanize(capitalize: false)}" "Close this #{issuable.to_ability_name.humanize(capitalize: false)}"
end end
condition do condition do
noteable.persisted? && issuable.persisted? &&
noteable.open? && issuable.open? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable) current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end end
command :close do command :close do
@updates[:state_event] = 'close' @updates[:state_event] = 'close'
end end
desc do desc do
"Reopen this #{noteable.to_ability_name.humanize(capitalize: false)}" "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}"
end end
condition do condition do
noteable.closed? && issuable.persisted? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable) issuable.closed? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end end
command :reopen, :open do command :reopen, :open do
@updates[:state_event] = 'reopen' @updates[:state_event] = 'reopen'
...@@ -60,8 +61,8 @@ module SlashCommands ...@@ -60,8 +61,8 @@ module SlashCommands
desc 'Change title' desc 'Change title'
params '<New title>' params '<New title>'
condition do condition do
noteable.persisted? && issuable.persisted? &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable) current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end end
command :title do |title_param| command :title do |title_param|
@updates[:title] = title_param @updates[:title] = title_param
...@@ -70,7 +71,7 @@ module SlashCommands ...@@ -70,7 +71,7 @@ module SlashCommands
desc 'Assign' desc 'Assign'
params '@user' params '@user'
condition do condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :assign do |assignee_param| command :assign do |assignee_param|
user = extract_references(assignee_param, :user).first user = extract_references(assignee_param, :user).first
...@@ -82,8 +83,9 @@ module SlashCommands ...@@ -82,8 +83,9 @@ module SlashCommands
desc 'Remove assignee' desc 'Remove assignee'
condition do condition do
noteable.assignee_id? && issuable.persisted? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project) issuable.assignee_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :unassign, :remove_assignee do command :unassign, :remove_assignee do
@updates[:assignee_id] = nil @updates[:assignee_id] = nil
...@@ -92,7 +94,7 @@ module SlashCommands ...@@ -92,7 +94,7 @@ module SlashCommands
desc 'Set milestone' desc 'Set milestone'
params '%"milestone"' params '%"milestone"'
condition do condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project) && current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
project.milestones.active.any? project.milestones.active.any?
end end
command :milestone do |milestone_param| command :milestone do |milestone_param|
...@@ -104,8 +106,9 @@ module SlashCommands ...@@ -104,8 +106,9 @@ module SlashCommands
desc 'Remove milestone' desc 'Remove milestone'
condition do condition do
noteable.milestone_id? && issuable.persisted? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project) issuable.milestone_id? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :clear_milestone, :remove_milestone do command :clear_milestone, :remove_milestone do
@updates[:milestone_id] = nil @updates[:milestone_id] = nil
...@@ -114,7 +117,7 @@ module SlashCommands ...@@ -114,7 +117,7 @@ module SlashCommands
desc 'Add label(s)' desc 'Add label(s)'
params '~label1 ~"label 2"' params '~label1 ~"label 2"'
condition do condition do
current_user.can?(:"admin_#{noteable.to_ability_name}", project) && current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
project.labels.any? project.labels.any?
end end
command :label, :labels do |labels_param| command :label, :labels do |labels_param|
...@@ -126,8 +129,9 @@ module SlashCommands ...@@ -126,8 +129,9 @@ module SlashCommands
desc 'Remove label(s)' desc 'Remove label(s)'
params '~label1 ~"label 2"' params '~label1 ~"label 2"'
condition do condition do
noteable.labels.any? && issuable.persisted? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project) issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :unlabel, :remove_label, :remove_labels do |labels_param| command :unlabel, :remove_label, :remove_labels do |labels_param|
label_ids = find_label_ids(labels_param) label_ids = find_label_ids(labels_param)
...@@ -137,8 +141,9 @@ module SlashCommands ...@@ -137,8 +141,9 @@ module SlashCommands
desc 'Remove all labels' desc 'Remove all labels'
condition do condition do
noteable.labels.any? && issuable.persisted? &&
current_user.can?(:"admin_#{noteable.to_ability_name}", project) issuable.labels.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :clear_labels, :clear_label do command :clear_labels, :clear_label do
@updates[:label_ids] = [] @updates[:label_ids] = []
...@@ -146,8 +151,8 @@ module SlashCommands ...@@ -146,8 +151,8 @@ module SlashCommands
desc 'Add a todo' desc 'Add a todo'
condition do condition do
noteable.persisted? && issuable.persisted? &&
!TodoService.new.todo_exist?(noteable, current_user) !TodoService.new.todo_exist?(issuable, current_user)
end end
command :todo do command :todo do
@updates[:todo_event] = 'add' @updates[:todo_event] = 'add'
...@@ -155,7 +160,8 @@ module SlashCommands ...@@ -155,7 +160,8 @@ module SlashCommands
desc 'Mark todo as done' desc 'Mark todo as done'
condition do condition do
TodoService.new.todo_exist?(noteable, current_user) issuable.persisted? &&
TodoService.new.todo_exist?(issuable, current_user)
end end
command :done do command :done do
@updates[:todo_event] = 'done' @updates[:todo_event] = 'done'
...@@ -163,8 +169,8 @@ module SlashCommands ...@@ -163,8 +169,8 @@ module SlashCommands
desc 'Subscribe' desc 'Subscribe'
condition do condition do
noteable.persisted? && issuable.persisted? &&
!noteable.subscribed?(current_user) !issuable.subscribed?(current_user)
end end
command :subscribe do command :subscribe do
@updates[:subscription_event] = 'subscribe' @updates[:subscription_event] = 'subscribe'
...@@ -172,8 +178,8 @@ module SlashCommands ...@@ -172,8 +178,8 @@ module SlashCommands
desc 'Unsubscribe' desc 'Unsubscribe'
condition do condition do
noteable.persisted? && issuable.persisted? &&
noteable.subscribed?(current_user) issuable.subscribed?(current_user)
end end
command :unsubscribe do command :unsubscribe do
@updates[:subscription_event] = 'unsubscribe' @updates[:subscription_event] = 'unsubscribe'
...@@ -182,8 +188,8 @@ module SlashCommands ...@@ -182,8 +188,8 @@ module SlashCommands
desc 'Set due date' desc 'Set due date'
params '<in 2 days; this Friday; December 31st>' params '<in 2 days; this Friday; December 31st>'
condition do condition do
noteable.respond_to?(:due_date) && issuable.respond_to?(:due_date) &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable) current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end end
command :due, :due_date do |due_date_param| command :due, :due_date do |due_date_param|
due_date = Chronic.parse(due_date_param).try(:to_date) due_date = Chronic.parse(due_date_param).try(:to_date)
...@@ -193,9 +199,10 @@ module SlashCommands ...@@ -193,9 +199,10 @@ module SlashCommands
desc 'Remove due date' desc 'Remove due date'
condition do condition do
noteable.respond_to?(:due_date) && issuable.persisted? &&
noteable.due_date? && issuable.respond_to?(:due_date) &&
current_user.can?(:"update_#{noteable.to_ability_name}", noteable) issuable.due_date? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end end
command :clear_due_date do command :clear_due_date do
@updates[:due_date] = nil @updates[:due_date] = nil
......
...@@ -3,8 +3,8 @@ module Gitlab ...@@ -3,8 +3,8 @@ module Gitlab
class CommandDefinition class CommandDefinition
attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block
def valid? def initialize(name)
name.present? @name = name
end end
def all_names def all_names
...@@ -22,13 +22,6 @@ module Gitlab ...@@ -22,13 +22,6 @@ module Gitlab
context.instance_exec(&condition_block) context.instance_exec(&condition_block)
end end
def to_description(opts)
return description unless description.respond_to?(:call)
context = OpenStruct.new(opts)
context.instance_exec(&description) rescue ''
end
def execute(context, opts, *args) def execute(context, opts, *args)
return if noop? || !available?(opts) return if noop? || !available?(opts)
......
...@@ -73,16 +73,13 @@ module Gitlab ...@@ -73,16 +73,13 @@ module Gitlab
def command(*command_names, &block) def command(*command_names, &block)
name, *aliases = command_names name, *aliases = command_names
definition = CommandDefinition.new definition = CommandDefinition.new(name)
definition.name = name
definition.aliases = aliases definition.aliases = aliases
definition.description = @description || '' definition.description = @description || ''
definition.params = @params || [] definition.params = @params || []
definition.condition_block = @condition_block definition.condition_block = @condition_block
definition.action_block = block definition.action_block = block
return unless definition.valid?
self.command_definitions << definition self.command_definitions << definition
definition.all_names.each do |name| definition.all_names.each do |name|
......
...@@ -29,8 +29,8 @@ module Gitlab ...@@ -29,8 +29,8 @@ module Gitlab
# commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']]
# msg #=> "hello\nworld" # msg #=> "hello\nworld"
# ``` # ```
def extract_commands(content, opts) def extract_commands(content, opts = {})
return [] unless content return [content, []] unless content
content = content.dup content = content.dup
...@@ -107,7 +107,13 @@ module Gitlab ...@@ -107,7 +107,13 @@ module Gitlab
# Command not in a blockquote, blockcode, or HTML tag: # Command not in a blockquote, blockcode, or HTML tag:
# /close # /close
^\/(?<cmd>#{Regexp.union(names)})(?:$|\ (?<args>[^\/\n]*)$) ^\/
(?<cmd>#{Regexp.union(names)})
(?:
[ ]
(?<args>[^\/\n]*)
)?
(?:\n|$)
) )
}mx }mx
end end
......
require 'spec_helper'
describe Gitlab::SlashCommands::CommandDefinition do
subject { described_class.new(:command) }
describe "#all_names" do
context "when the command has aliases" do
before do
subject.aliases = [:alias1, :alias2]
end
it "returns an array with the name and aliases" do
expect(subject.all_names).to eq([:command, :alias1, :alias2])
end
end
context "when the command doesn't have aliases" do
it "returns an array with the name" do
expect(subject.all_names).to eq([:command])
end
end
end
describe "#noop?" do
context "when the command has an action block" do
before do
subject.action_block = -> { }
end
it "returns false" do
expect(subject.noop?).to be false
end
end
context "when the command doesn't have an action block" do
it "returns true" do
expect(subject.noop?).to be true
end
end
end
describe "#available?" do
let(:opts) { { go: false } }
context "when the command has a condition block" do
before do
subject.condition_block = -> { go }
end
context "when the condition block returns true" do
before do
opts[:go] = true
end
it "returns true" do
expect(subject.available?(opts)).to be true
end
end
context "when the condition block returns false" do
it "returns false" do
expect(subject.available?(opts)).to be false
end
end
end
context "when the command doesn't have a condition block" do
it "returns true" do
expect(subject.available?(opts)).to be true
end
end
end
describe "#execute" do
let(:context) { OpenStruct.new(run: false) }
context "when the command is a noop" do
it "doesn't execute the command" do
expect(context).not_to receive(:instance_exec)
subject.execute(context, {})
expect(context.run).to be false
end
end
context "when the command is not a noop" do
before do
subject.action_block = -> { self.run = true }
end
context "when the command is not available" do
before do
subject.condition_block = -> { false }
end
it "doesn't execute the command" do
subject.execute(context, {})
expect(context.run).to be false
end
end
context "when the command is available" do
context "when the command has an exact number of arguments" do
before do
subject.action_block = ->(arg) { self.run = arg }
end
context "when the command is provided a wrong number of arguments" do
it "doesn't execute the command" do
subject.execute(context, {}, true, true)
expect(context.run).to be false
end
end
context "when the command is provided the right number of arguments" do
it "executes the command" do
subject.execute(context, {}, true)
expect(context.run).to be true
end
end
end
context "when the command has a variable number of arguments" do
before do
subject.action_block = ->(*args) { self.run = args.first }
end
context "when the command is provided any number of arguments" do
it "executes the command" do
subject.execute(context, {}, true, true)
expect(context.run).to be true
end
end
end
end
end
end
end
...@@ -10,9 +10,9 @@ describe Gitlab::SlashCommands::Dsl do ...@@ -10,9 +10,9 @@ describe Gitlab::SlashCommands::Dsl do
"Hello World!" "Hello World!"
end end
desc 'A command returning a value' desc { "A command with #{something}" }
command :returning do command :returning do
return 42 42
end end
params 'The first argument' params 'The first argument'
...@@ -28,7 +28,7 @@ describe Gitlab::SlashCommands::Dsl do ...@@ -28,7 +28,7 @@ describe Gitlab::SlashCommands::Dsl do
[arg1, arg2] [arg1, arg2]
end end
command :cc, noop: true command :cc
condition do condition do
project == 'foo' project == 'foo'
...@@ -49,182 +49,74 @@ describe Gitlab::SlashCommands::Dsl do ...@@ -49,182 +49,74 @@ describe Gitlab::SlashCommands::Dsl do
{ {
name: :no_args, aliases: [:none], name: :no_args, aliases: [:none],
description: 'A command with no args', params: [], description: 'A command with no args', params: [],
condition_block: nil, action_block: a_kind_of(Proc), condition_block: nil, action_block: a_kind_of(Proc)
opts: {}
}, },
{ {
name: :returning, aliases: [], name: :returning, aliases: [],
description: 'A command returning a value', params: [], description: 'A command returning a value', params: [],
condition_block: nil, action_block: a_kind_of(Proc), condition_block: nil, action_block: a_kind_of(Proc)
opts: {}
}, },
{ {
name: :one_arg, aliases: [:once, :first], name: :one_arg, aliases: [:once, :first],
description: '', params: ['The first argument'], description: '', params: ['The first argument'],
condition_block: nil, action_block: a_kind_of(Proc), condition_block: nil, action_block: a_kind_of(Proc)
opts: {}
}, },
{ {
name: :two_args, aliases: [], name: :two_args, aliases: [],
description: '', params: ['The first argument', 'The second argument'], description: '', params: ['The first argument', 'The second argument'],
condition_block: nil, action_block: a_kind_of(Proc), condition_block: nil, action_block: a_kind_of(Proc)
opts: {}
}, },
{ {
name: :cc, aliases: [], name: :cc, aliases: [],
description: '', params: [], description: '', params: [],
condition_block: nil, action_block: nil, condition_block: nil, action_block: nil
opts: { noop: true }
}, },
{ {
name: :wildcard, aliases: [], name: :wildcard, aliases: [],
description: '', params: [], description: '', params: [],
condition_block: nil, action_block: a_kind_of(Proc), condition_block: nil, action_block: a_kind_of(Proc)
opts: {}
} }
] ]
end end
it 'returns an array with commands definitions' do it 'returns an array with commands definitions' do
expect(DummyClass.command_definitions).to match_array base_expected no_args_def, returning_def, one_arg_def, two_args_def, cc_def, cond_action_def, wildcard_def = DummyClass.command_definitions
end
expect(no_args_def.name).to eq(:no_args)
context 'with options passed' do expect(no_args_def.aliases).to eq([:none])
context 'when condition is met' do expect(no_args_def.description).to eq('A command with no args')
let(:expected) do expect(no_args_def.params).to eq([])
base_expected << { expect(no_args_def.condition_block).to be_nil
name: :cond_action, aliases: [], expect(no_args_def.action_block).to be_a_kind_of(Proc)
description: '', params: [],
condition_block: a_kind_of(Proc), action_block: a_kind_of(Proc), expect(returning_def.name).to eq(:returning)
opts: {} expect(returning_def.aliases).to eq([])
} expect(returning_def.description).to be_a_kind_of(Proc)
end expect(returning_def.to_h(something: "a block description")[:description]).to eq('A command with a block description')
expect(returning_def.params).to eq([])
it 'returns an array with commands definitions' do expect(returning_def.condition_block).to be_nil
expect(DummyClass.command_definitions(project: 'foo')).to match_array expected expect(returning_def.action_block).to be_a_kind_of(Proc)
end
end expect(one_arg_def.name).to eq(:one_arg)
expect(one_arg_def.aliases).to eq([:once, :first])
context 'when condition is not met' do expect(one_arg_def.description).to eq('')
it 'returns an array with commands definitions without actions that did not met conditions' do expect(one_arg_def.params).to eq(['The first argument'])
expect(DummyClass.command_definitions(project: 'bar')).to match_array base_expected expect(one_arg_def.condition_block).to be_nil
end expect(one_arg_def.action_block).to be_a_kind_of(Proc)
end
expect(cc_def.name).to eq(:cc)
context 'when description can be generated dynamically' do expect(cc_def.aliases).to eq([])
it 'returns an array with commands definitions with dynamic descriptions' do expect(cc_def.description).to eq('')
base_expected[3][:description] = 'A dynamic description for MERGE REQUEST' expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
expect(DummyClass.command_definitions(noteable: 'merge request')).to match_array base_expected expect(cc_def.action_block).to be_nil
end
end expect(wildcard_def.name).to eq(:wildcard)
end expect(wildcard_def.aliases).to eq([])
end expect(wildcard_def.description).to eq('')
expect(wildcard_def.params).to eq([])
describe '.command_names' do expect(wildcard_def.condition_block).to be_nil
let(:base_expected) do expect(wildcard_def.action_block).to be_a_kind_of(Proc)
[
:no_args, :none, :returning, :one_arg,
:once, :first, :two_args, :wildcard
]
end
it 'returns an array with commands definitions' do
expect(DummyClass.command_names).to eq base_expected
end
context 'with options passed' do
context 'when condition is met' do
let(:expected) { base_expected << :cond_action }
it 'returns an array with commands definitions' do
expect(DummyClass.command_names(project: 'foo')).to match_array expected
end
end
context 'when condition is not met' do
it 'returns an array with commands definitions without action that did not met conditions' do
expect(DummyClass.command_names(project: 'bar')).to match_array base_expected
end
end
end
end
let(:dummy) { DummyClass.new(nil) }
describe '#execute_command' do
describe 'command with no args' do
context 'called with no args' do
it 'succeeds' do
expect(dummy.execute_command(:no_args)).to eq 'Hello World!'
end
end
end
describe 'command with an explicit return' do
context 'called with no args' do
it 'succeeds' do
expect { dummy.execute_command(:returning) }.to raise_error(LocalJumpError)
end
end
end
describe 'command with one arg' do
context 'called with one arg' do
it 'succeeds' do
expect(dummy.execute_command(:one_arg, 42)).to eq 42
end
end
end
describe 'command with two args' do
context 'called with two args' do
it 'succeeds' do
expect(dummy.execute_command(:two_args, 42, 'foo')).to eq [42, 'foo']
end
end
end
describe 'noop command' do
it 'returns nil' do
expect(dummy.execute_command(:cc)).to be_nil
end
end
describe 'command with condition' do
context 'when condition is not met' do
it 'returns nil' do
expect(dummy.execute_command(:cond_action)).to be_nil
end
end
context 'when condition is met' do
let(:dummy) { DummyClass.new('foo') }
it 'succeeds' do
expect(dummy.execute_command(:cond_action, 42)).to eq 42
end
end
end
describe 'command with wildcard' do
context 'called with no args' do
it 'succeeds' do
expect(dummy.execute_command(:wildcard)).to eq []
end
end
context 'called with one arg' do
it 'succeeds' do
expect(dummy.execute_command(:wildcard, 42)).to eq [42]
end
end
context 'called with two args' do
it 'succeeds' do
expect(dummy.execute_command(:wildcard, 42, 'foo')).to eq [42, 'foo']
end
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::SlashCommands::Extractor do describe Gitlab::SlashCommands::Extractor do
let(:extractor) { described_class.new([:open, :assign, :labels, :power]) } let(:definitions) do
Class.new do
include Gitlab::SlashCommands::Dsl
command(:reopen, :open) { }
command(:assign) { }
command(:labels) { }
command(:power) { }
end.command_definitions
end
let(:extractor) { described_class.new(definitions) }
shared_examples 'command with no argument' do shared_examples 'command with no argument' do
it 'extracts command' do it 'extracts command' do
commands = extractor.extract_commands(original_msg) msg, commands = extractor.extract_commands(original_msg)
expect(commands).to eq [['open']] expect(commands).to eq [['open']]
expect(original_msg).to eq final_msg expect(msg).to eq final_msg
end end
end end
shared_examples 'command with a single argument' do shared_examples 'command with a single argument' do
it 'extracts command' do it 'extracts command' do
commands = extractor.extract_commands(original_msg) msg, commands = extractor.extract_commands(original_msg)
expect(commands).to eq [['assign', '@joe']] expect(commands).to eq [['assign', '@joe']]
expect(original_msg).to eq final_msg expect(msg).to eq final_msg
end end
end end
shared_examples 'command with multiple arguments' do shared_examples 'command with multiple arguments' do
it 'extracts command' do it 'extracts command' do
commands = extractor.extract_commands(original_msg) msg, commands = extractor.extract_commands(original_msg)
expect(commands).to eq [['labels', '~foo ~"bar baz" label']] expect(commands).to eq [['labels', '~foo ~"bar baz" label']]
expect(original_msg).to eq final_msg expect(msg).to eq final_msg
end end
end end
...@@ -49,7 +60,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -49,7 +60,7 @@ describe Gitlab::SlashCommands::Extractor do
context 'in the middle of a line' do context 'in the middle of a line' do
it 'does not extract command' do it 'does not extract command' do
msg = "hello\nworld /open" msg = "hello\nworld /open"
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq "hello\nworld /open" expect(msg).to eq "hello\nworld /open"
...@@ -59,7 +70,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -59,7 +70,7 @@ describe Gitlab::SlashCommands::Extractor do
context 'at the end of content' do context 'at the end of content' do
it_behaves_like 'command with no argument' do it_behaves_like 'command with no argument' do
let(:original_msg) { "hello\n/open" } let(:original_msg) { "hello\n/open" }
let(:final_msg) { "hello\n" } let(:final_msg) { "hello" }
end end
end end
end end
...@@ -82,7 +93,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -82,7 +93,7 @@ describe Gitlab::SlashCommands::Extractor do
context 'in the middle of a line' do context 'in the middle of a line' do
it 'does not extract command' do it 'does not extract command' do
msg = "hello\nworld /assign @joe" msg = "hello\nworld /assign @joe"
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq "hello\nworld /assign @joe" expect(msg).to eq "hello\nworld /assign @joe"
...@@ -92,14 +103,14 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -92,14 +103,14 @@ describe Gitlab::SlashCommands::Extractor do
context 'at the end of content' do context 'at the end of content' do
it_behaves_like 'command with a single argument' do it_behaves_like 'command with a single argument' do
let(:original_msg) { "hello\n/assign @joe" } let(:original_msg) { "hello\n/assign @joe" }
let(:final_msg) { "hello\n" } let(:final_msg) { "hello" }
end end
end end
context 'when argument is not separated with a space' do context 'when argument is not separated with a space' do
it 'does not extract command' do it 'does not extract command' do
msg = "hello\n/assign@joe\nworld" msg = "hello\n/assign@joe\nworld"
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq "hello\n/assign@joe\nworld" expect(msg).to eq "hello\n/assign@joe\nworld"
...@@ -125,7 +136,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -125,7 +136,7 @@ describe Gitlab::SlashCommands::Extractor do
context 'in the middle of a line' do context 'in the middle of a line' do
it 'does not extract command' do it 'does not extract command' do
msg = %(hello\nworld /labels ~foo ~"bar baz" label) msg = %(hello\nworld /labels ~foo ~"bar baz" label)
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq %(hello\nworld /labels ~foo ~"bar baz" label) expect(msg).to eq %(hello\nworld /labels ~foo ~"bar baz" label)
...@@ -135,14 +146,14 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -135,14 +146,14 @@ describe Gitlab::SlashCommands::Extractor do
context 'at the end of content' do context 'at the end of content' do
it_behaves_like 'command with multiple arguments' do it_behaves_like 'command with multiple arguments' do
let(:original_msg) { %(hello\n/labels ~foo ~"bar baz" label) } let(:original_msg) { %(hello\n/labels ~foo ~"bar baz" label) }
let(:final_msg) { "hello\n" } let(:final_msg) { "hello" }
end end
end end
context 'when argument is not separated with a space' do context 'when argument is not separated with a space' do
it 'does not extract command' do it 'does not extract command' do
msg = %(hello\n/labels~foo ~"bar baz" label\nworld) msg = %(hello\n/labels~foo ~"bar baz" label\nworld)
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq %(hello\n/labels~foo ~"bar baz" label\nworld) expect(msg).to eq %(hello\n/labels~foo ~"bar baz" label\nworld)
...@@ -152,7 +163,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -152,7 +163,7 @@ describe Gitlab::SlashCommands::Extractor do
it 'extracts command with multiple arguments and various prefixes' do it 'extracts command with multiple arguments and various prefixes' do
msg = %(hello\n/power @user.name %9.10 ~"bar baz.2"\nworld) msg = %(hello\n/power @user.name %9.10 ~"bar baz.2"\nworld)
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2"']] expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2"']]
expect(msg).to eq "hello\nworld" expect(msg).to eq "hello\nworld"
...@@ -160,15 +171,15 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -160,15 +171,15 @@ describe Gitlab::SlashCommands::Extractor do
it 'extracts multiple commands' do it 'extracts multiple commands' do
msg = %(hello\n/power @user.name %9.10 ~"bar baz.2" label\nworld\n/open) msg = %(hello\n/power @user.name %9.10 ~"bar baz.2" label\nworld\n/open)
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2" label'], ['open']] expect(commands).to eq [['power', '@user.name %9.10 ~"bar baz.2" label'], ['open']]
expect(msg).to eq "hello\nworld\n" expect(msg).to eq "hello\nworld"
end end
it 'does not alter original content if no command is found' do it 'does not alter original content if no command is found' do
msg = 'Fixes #123' msg = 'Fixes #123'
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq 'Fixes #123' expect(msg).to eq 'Fixes #123'
...@@ -177,7 +188,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -177,7 +188,7 @@ describe Gitlab::SlashCommands::Extractor do
it 'does not extract commands inside a blockcode' do it 'does not extract commands inside a blockcode' do
msg = "Hello\r\n```\r\nThis is some text\r\n/close\r\n/assign @user\r\n```\r\n\r\nWorld" msg = "Hello\r\n```\r\nThis is some text\r\n/close\r\n/assign @user\r\n```\r\n\r\nWorld"
expected = msg.delete("\r") expected = msg.delete("\r")
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq expected expect(msg).to eq expected
...@@ -186,7 +197,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -186,7 +197,7 @@ describe Gitlab::SlashCommands::Extractor do
it 'does not extract commands inside a blockquote' do it 'does not extract commands inside a blockquote' do
msg = "Hello\r\n>>>\r\nThis is some text\r\n/close\r\n/assign @user\r\n>>>\r\n\r\nWorld" msg = "Hello\r\n>>>\r\nThis is some text\r\n/close\r\n/assign @user\r\n>>>\r\n\r\nWorld"
expected = msg.delete("\r") expected = msg.delete("\r")
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq expected expect(msg).to eq expected
...@@ -195,7 +206,7 @@ describe Gitlab::SlashCommands::Extractor do ...@@ -195,7 +206,7 @@ describe Gitlab::SlashCommands::Extractor do
it 'does not extract commands inside a HTML tag' do it 'does not extract commands inside a HTML tag' do
msg = "Hello\r\n<div>\r\nThis is some text\r\n/close\r\n/assign @user\r\n</div>\r\n\r\nWorld" msg = "Hello\r\n<div>\r\nThis is some text\r\n/close\r\n/assign @user\r\n</div>\r\n\r\nWorld"
expected = msg.delete("\r") expected = msg.delete("\r")
commands = extractor.extract_commands(msg) msg, commands = extractor.extract_commands(msg)
expect(commands).to be_empty expect(commands).to be_empty
expect(msg).to eq expected expect(msg).to eq expected
......
...@@ -56,7 +56,7 @@ describe Notes::CreateService, services: true do ...@@ -56,7 +56,7 @@ describe Notes::CreateService, services: true do
it "creates regular note if emoji name is invalid" do it "creates regular note if emoji name is invalid" do
opts = { opts = {
note: ':smile: moretext: ', note: ':smile: moretext:',
noteable_type: 'Issue', noteable_type: 'Issue',
noteable_id: issue.id noteable_id: issue.id
} }
......
...@@ -12,7 +12,6 @@ describe Notes::SlashCommandsService, services: true do ...@@ -12,7 +12,6 @@ describe Notes::SlashCommandsService, services: true do
before do before do
note.note = note_text note.note = note_text
described_class.new(project, master).execute(note)
end end
describe 'note with only command' do describe 'note with only command' do
...@@ -20,7 +19,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -20,7 +19,10 @@ describe Notes::SlashCommandsService, services: true do
let(:note_text) { %(/close\n/assign @#{assignee.username}") } let(:note_text) { %(/close\n/assign @#{assignee.username}") }
it 'saves the note and does not alter the note text' do it 'saves the note and does not alter the note text' do
expect(note.note).to eq note_text content, command_params = service.extract_commands(note)
expect(content).to eq note_text
expect(command_params).to be_empty
end end
end end
end end
...@@ -30,7 +32,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -30,7 +32,10 @@ describe Notes::SlashCommandsService, services: true do
let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) } let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) }
it 'saves the note and does not alter the note text' do it 'saves the note and does not alter the note text' do
expect(note.note).to eq note_text content, command_params = service.extract_commands(note)
expect(content).to eq note_text
expect(command_params).to be_empty
end end
end end
end end
...@@ -53,9 +58,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -53,9 +58,10 @@ describe Notes::SlashCommandsService, services: true do
end end
it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do it 'closes noteable, sets labels, assigns, and sets milestone to noteable, and leave no note' do
described_class.new(project, master).execute(note) content, command_params = service.extract_commands(note)
service.execute(command_params, note)
expect(note.note).to eq '' expect(content).to eq ''
expect(note.noteable).to be_closed expect(note.noteable).to be_closed
expect(note.noteable.labels).to match_array(labels) expect(note.noteable.labels).to match_array(labels)
expect(note.noteable.assignee).to eq(assignee) expect(note.noteable.assignee).to eq(assignee)
...@@ -71,9 +77,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -71,9 +77,10 @@ describe Notes::SlashCommandsService, services: true do
let(:note_text) { '/open' } let(:note_text) { '/open' }
it 'opens the noteable, and leave no note' do it 'opens the noteable, and leave no note' do
described_class.new(project, master).execute(note) content, command_params = service.extract_commands(note)
service.execute(command_params, note)
expect(note.note).to eq '' expect(content).to eq ''
expect(note.noteable).to be_open expect(note.noteable).to be_open
end end
end end
...@@ -86,9 +93,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -86,9 +93,10 @@ describe Notes::SlashCommandsService, services: true do
end end
it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do
described_class.new(project, master).execute(note) content, command_params = service.extract_commands(note)
service.execute(command_params, note)
expect(note.note).to eq "HELLO\nWORLD" expect(content).to eq "HELLO\nWORLD"
expect(note.noteable).to be_closed expect(note.noteable).to be_closed
expect(note.noteable.labels).to match_array(labels) expect(note.noteable.labels).to match_array(labels)
expect(note.noteable.assignee).to eq(assignee) expect(note.noteable.assignee).to eq(assignee)
...@@ -104,9 +112,10 @@ describe Notes::SlashCommandsService, services: true do ...@@ -104,9 +112,10 @@ describe Notes::SlashCommandsService, services: true do
let(:note_text) { "HELLO\n/open\nWORLD" } let(:note_text) { "HELLO\n/open\nWORLD" }
it 'opens the noteable' do it 'opens the noteable' do
described_class.new(project, master).execute(note) content, command_params = service.extract_commands(note)
service.execute(command_params, note)
expect(note.note).to eq "HELLO\nWORLD" expect(content).to eq "HELLO\nWORLD"
expect(note.noteable).to be_open expect(note.noteable).to be_open
end end
end end
...@@ -114,6 +123,8 @@ describe Notes::SlashCommandsService, services: true do ...@@ -114,6 +123,8 @@ describe Notes::SlashCommandsService, services: true do
end end
describe '#execute' do describe '#execute' do
let(:service) { described_class.new(project, master) }
it_behaves_like 'note on noteable that supports slash commands' do it_behaves_like 'note on noteable that supports slash commands' do
let(:note) { build(:note_on_issue, project: project) } let(:note) { build(:note_on_issue, project: project) }
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