slackrb 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.rubocop.yml +29 -0
- data/.rubocop_todo.yml +49 -0
- data/CHANGELOG.md +219 -0
- data/Dangerfile +5 -0
- data/Gemfile +20 -0
- data/LICENSE.md +22 -0
- data/README.md +766 -0
- data/Rakefile +21 -0
- data/lib/config/application.rb +14 -0
- data/lib/config/boot.rb +8 -0
- data/lib/config/environment.rb +5 -0
- data/lib/slack-ruby-bot/about.rb +9 -0
- data/lib/slack-ruby-bot/app.rb +56 -0
- data/lib/slack-ruby-bot/bot.rb +19 -0
- data/lib/slack-ruby-bot/client.rb +65 -0
- data/lib/slack-ruby-bot/commands/about.rb +14 -0
- data/lib/slack-ruby-bot/commands/base.rb +158 -0
- data/lib/slack-ruby-bot/commands/help.rb +43 -0
- data/lib/slack-ruby-bot/commands/hi.rb +16 -0
- data/lib/slack-ruby-bot/commands/support/attrs.rb +36 -0
- data/lib/slack-ruby-bot/commands/support/help.rb +84 -0
- data/lib/slack-ruby-bot/commands/support/match.rb +23 -0
- data/lib/slack-ruby-bot/commands/unknown.rb +13 -0
- data/lib/slack-ruby-bot/commands.rb +7 -0
- data/lib/slack-ruby-bot/config.rb +38 -0
- data/lib/slack-ruby-bot/hooks/hello.rb +35 -0
- data/lib/slack-ruby-bot/hooks/hook_support.rb +45 -0
- data/lib/slack-ruby-bot/hooks/message.rb +56 -0
- data/lib/slack-ruby-bot/hooks/set.rb +45 -0
- data/lib/slack-ruby-bot/hooks.rb +6 -0
- data/lib/slack-ruby-bot/mvc/controller/base.rb +172 -0
- data/lib/slack-ruby-bot/mvc/model/base.rb +27 -0
- data/lib/slack-ruby-bot/mvc/mvc.rb +7 -0
- data/lib/slack-ruby-bot/mvc/view/base.rb +30 -0
- data/lib/slack-ruby-bot/mvc.rb +3 -0
- data/lib/slack-ruby-bot/rspec/support/bots_for_tests.rb +35 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb +16 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/not_respond.rb +25 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb +36 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb +34 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_messages.rb +45 -0
- data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/start_typing.rb +32 -0
- data/lib/slack-ruby-bot/rspec/support/slack_api_key.rb +10 -0
- data/lib/slack-ruby-bot/rspec/support/slack_ruby_bot_configure.rb +15 -0
- data/lib/slack-ruby-bot/rspec/support/spec_helpers.rb +14 -0
- data/lib/slack-ruby-bot/rspec.rb +14 -0
- data/lib/slack-ruby-bot/server.rb +88 -0
- data/lib/slack-ruby-bot/support/loggable.rb +25 -0
- data/lib/slack-ruby-bot/version.rb +5 -0
- data/lib/slack-ruby-bot.rb +29 -0
- data/lib/slack_ruby_bot.rb +3 -0
- data/screenshots/aliases.gif +0 -0
- data/screenshots/create-classic-app.png +0 -0
- data/screenshots/demo.gif +0 -0
- data/screenshots/dms.gif +0 -0
- data/screenshots/help.png +0 -0
- data/screenshots/market.gif +0 -0
- data/screenshots/weather.gif +0 -0
- data/slack-ruby-bot.gemspec +32 -0
- data/slack.png +0 -0
- metadata +244 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module Hooks
|
5
|
+
module HookSupport
|
6
|
+
def self.included(base)
|
7
|
+
base.cattr_accessor :hook_blocks
|
8
|
+
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def on(event_name, &block)
|
14
|
+
self.hook_blocks ||= {}
|
15
|
+
|
16
|
+
self.hook_blocks[event_name] ||= []
|
17
|
+
self.hook_blocks[event_name] << block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def on(event_name, handler)
|
22
|
+
_hooks.add(event_name, handler)
|
23
|
+
end
|
24
|
+
|
25
|
+
def flush_hook_blocks
|
26
|
+
return nil unless self.class.hook_blocks
|
27
|
+
|
28
|
+
add_hook_handlers(self.class.hook_blocks)
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: This should be deprecated in favor of `on`
|
32
|
+
def add_hook_handlers(handler_hash)
|
33
|
+
handler_hash.each do |hook, handlers|
|
34
|
+
Array(handlers).each { |handler| on(hook, handler) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Temp use this method in order to deprecate `hooks` and revisit
|
39
|
+
def _hooks
|
40
|
+
@hooks ||= SlackRubyBot::Hooks::Set.new
|
41
|
+
end
|
42
|
+
private :_hooks
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module Hooks
|
5
|
+
class Message
|
6
|
+
def call(client, data)
|
7
|
+
return if !client.allow_message_loops? && client.message_to_self?(data)
|
8
|
+
return if !client.allow_bot_messages? && client.bot_message?(data)
|
9
|
+
|
10
|
+
prepare!(data)
|
11
|
+
|
12
|
+
result = child_command_classes.detect { |d| d.invoke(client, data) }
|
13
|
+
result ||= built_in_command_classes.detect { |d| d.invoke(client, data) }
|
14
|
+
result ||= SlackRubyBot::Commands::Unknown.tap { |d| d.invoke(client, data) }
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def prepare!(data)
|
21
|
+
data.text = data.text.strip if data.text
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# All commands.
|
26
|
+
#
|
27
|
+
# @return [Array] Descendants of SlackRubyBot::Commands::Base.
|
28
|
+
#
|
29
|
+
def command_classes
|
30
|
+
SlackRubyBot::Commands::Base.command_classes
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# All non-built-in, ie. custom commands.
|
35
|
+
#
|
36
|
+
# @return [Array] Non-built-in descendants of SlackRubyBot::Commands::Base.
|
37
|
+
#
|
38
|
+
def child_command_classes
|
39
|
+
command_classes.reject do |k|
|
40
|
+
k.name&.starts_with?('SlackRubyBot::Commands::')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# All built-in commands.
|
46
|
+
#
|
47
|
+
# @return [Array] Built-in descendants of SlackRubyBot::Commands::Base.
|
48
|
+
#
|
49
|
+
def built_in_command_classes
|
50
|
+
command_classes.select do |k|
|
51
|
+
k.name&.starts_with?('SlackRubyBot::Commands::') && k != SlackRubyBot::Commands::Unknown
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module Hooks
|
5
|
+
class Set
|
6
|
+
attr_accessor :handlers, :client
|
7
|
+
|
8
|
+
def initialize(client = nil)
|
9
|
+
self.handlers = {}
|
10
|
+
self.client = client
|
11
|
+
|
12
|
+
@pending_flush = client.blank?
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(hook_name, handler)
|
16
|
+
handlers[hook_name] ||= ::Set.new
|
17
|
+
handlers[hook_name] << handler
|
18
|
+
|
19
|
+
register_callback(hook_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def client=(client)
|
23
|
+
@client = client
|
24
|
+
|
25
|
+
flush_handlers if @pending_flush
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def register_callback(hook_name)
|
31
|
+
return unless client # We'll delay this until client is set
|
32
|
+
|
33
|
+
client.on hook_name do |data|
|
34
|
+
handlers[hook_name].each do |handler|
|
35
|
+
handler.call(client, data)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def flush_handlers
|
41
|
+
handlers.each_key { |hook| register_callback(hook) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module MVC
|
5
|
+
module Controller
|
6
|
+
class Base
|
7
|
+
include ActiveSupport::Callbacks
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_reader :abstract
|
11
|
+
alias abstract? abstract
|
12
|
+
|
13
|
+
def controllers
|
14
|
+
get_or_set_ivar(:@controllers, [])
|
15
|
+
end
|
16
|
+
|
17
|
+
def command_class
|
18
|
+
get_or_set_ivar(:@command_class, Class.new(SlackRubyBot::Commands::Base))
|
19
|
+
end
|
20
|
+
|
21
|
+
def aliases
|
22
|
+
get_or_set_ivar(:@aliases, Hash.new { |h, k| h[k] = [] })
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_or_set_ivar(name, value)
|
26
|
+
unless (ivar = Base.instance_variable_get(name))
|
27
|
+
ivar = value
|
28
|
+
Base.instance_variable_set(name, ivar)
|
29
|
+
end
|
30
|
+
ivar
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset!
|
34
|
+
# Remove any earlier anonymous classes from prior calls so we don't leak them
|
35
|
+
Commands::Base.command_classes.delete(Controller::Base.command_class) if Base.command_class
|
36
|
+
|
37
|
+
Base.instance_variable_set(:@command_class, nil)
|
38
|
+
Base.instance_variable_set(:@controllers, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Define a controller as abstract. See internal_methods for more
|
42
|
+
# details.
|
43
|
+
def abstract!
|
44
|
+
@abstract = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def inherited(klass) # :nodoc:
|
48
|
+
# Define the abstract ivar on subclasses so that we don't get
|
49
|
+
# uninitialized ivar warnings
|
50
|
+
klass.instance_variable_set(:@abstract, false) unless klass.instance_variable_defined?(:@abstract)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_controller(controller)
|
55
|
+
# Only used to keep a reference around so the instance object doesn't get garbage collected
|
56
|
+
controllers << controller
|
57
|
+
klass = controller.class
|
58
|
+
|
59
|
+
methods = (klass.public_instance_methods(true) -
|
60
|
+
# Except for public instance methods of Base and its ancestors
|
61
|
+
internal_methods(klass) +
|
62
|
+
# Be sure to include shadowed public instance methods of this class
|
63
|
+
klass.public_instance_methods(false)).uniq.map(&:to_s)
|
64
|
+
|
65
|
+
methods.each do |name|
|
66
|
+
next if name[0] == '_'
|
67
|
+
|
68
|
+
commands = lookup_command_name(name)
|
69
|
+
|
70
|
+
# Generates a command for each controller method *and* its aliases
|
71
|
+
commands.each do |command_string|
|
72
|
+
# sprinkle a little syntactic sugar on top of existing `command` infrastructure
|
73
|
+
command_class.class_eval do
|
74
|
+
command command_string do |client, data, match|
|
75
|
+
controller.use_args(client, data, match)
|
76
|
+
controller.call_command
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# A list of all internal methods for a controller. This finds the first
|
84
|
+
# abstract superclass of a controller, and gets a list of all public
|
85
|
+
# instance methods on that abstract class. Public instance methods of
|
86
|
+
# a controller would normally be considered action methods, so methods
|
87
|
+
# declared on abstract classes are being removed.
|
88
|
+
# (Controller::Base is defined as abstract)
|
89
|
+
def internal_methods(controller)
|
90
|
+
controller = controller.superclass until controller.abstract?
|
91
|
+
controller.public_instance_methods(true)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Maps a controller method name to an alternate command name. Used in cases where
|
95
|
+
# a command can be called via multiple text strings.
|
96
|
+
#
|
97
|
+
# Call this method *after* defining the original method.
|
98
|
+
#
|
99
|
+
# Class.new(SlackRubyBot::MVC::Controller::Base) do
|
100
|
+
# def quxo_foo_bar
|
101
|
+
# client.say(channel: data.channel, text: "quxo foo bar: #{match[:expression]}")
|
102
|
+
# end
|
103
|
+
# # setup alias name after original method definition
|
104
|
+
# alternate_name :quxo_foo_bar, :another_text_string
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# This is equivalent to:
|
108
|
+
#
|
109
|
+
# e.g.
|
110
|
+
# command 'quxo foo bar', 'another text string' do |*args|
|
111
|
+
# ..
|
112
|
+
# end
|
113
|
+
def alternate_name(original_name, *alias_names)
|
114
|
+
command_name = convert_method_name_to_command_string(original_name)
|
115
|
+
command_aliases = alias_names.map do |name|
|
116
|
+
convert_method_name_to_command_string(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
aliases[command_name] += command_aliases
|
120
|
+
|
121
|
+
alias_names.each { |alias_name| alias_method(alias_name, original_name) }
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def lookup_command_name(name)
|
127
|
+
name = convert_method_name_to_command_string(name)
|
128
|
+
[name] + aliases[name]
|
129
|
+
end
|
130
|
+
|
131
|
+
def convert_method_name_to_command_string(name)
|
132
|
+
name.to_s.tr('_', ' ')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
abstract!
|
137
|
+
reset!
|
138
|
+
|
139
|
+
attr_reader :model, :view, :client, :data, :match
|
140
|
+
|
141
|
+
def initialize(model, view)
|
142
|
+
@model = model
|
143
|
+
@view = view
|
144
|
+
self.class.register_controller(self)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Hand off the latest updated objects to the +model+ and +view+ and
|
148
|
+
# update our +client+, +data+, and +match+ accessors.
|
149
|
+
def use_args(client, data, match)
|
150
|
+
@client = client
|
151
|
+
@data = data
|
152
|
+
@match = match
|
153
|
+
model.use_args(client, data, match)
|
154
|
+
view.use_args(client, data, match)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Determine the command issued and call the corresponding instance method
|
158
|
+
def call_command
|
159
|
+
verb = match.captures[match.names.index('command')]
|
160
|
+
verb = normalize_command_string(verb)
|
161
|
+
public_send(verb)
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def normalize_command_string(string)
|
167
|
+
string.downcase.tr(' ', '_')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module MVC
|
5
|
+
module Model
|
6
|
+
class Base
|
7
|
+
include ActiveSupport::Callbacks
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def inherited(klass) # :nodoc:
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :client, :data, :match
|
16
|
+
|
17
|
+
# Hand off the latest updated objects to the +model+ and +view+ and
|
18
|
+
# update our +client+, +data+, and +match+ accessors.
|
19
|
+
def use_args(client, data, match)
|
20
|
+
@client = client
|
21
|
+
@data = data
|
22
|
+
@match = match
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module MVC
|
5
|
+
module View
|
6
|
+
class Base
|
7
|
+
include ActiveSupport::Callbacks
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def inherited(klass) # :nodoc:
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :client, :data, :match
|
17
|
+
|
18
|
+
def_delegators :@client, :say
|
19
|
+
|
20
|
+
# Hand off the latest updated objects to the +model+ and +view+ and
|
21
|
+
# update our +client+, +data+, and +match+ accessors.
|
22
|
+
def use_args(client, data, match)
|
23
|
+
@client = client
|
24
|
+
@data = data
|
25
|
+
@match = match
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Testing
|
4
|
+
class WeatherBot < SlackRubyBot::Bot
|
5
|
+
help do
|
6
|
+
title 'Weather Bot'
|
7
|
+
desc 'This bot tells you the weather.'
|
8
|
+
|
9
|
+
command 'clouds' do
|
10
|
+
desc 'Tells you how many clouds there\'re above you.'
|
11
|
+
end
|
12
|
+
|
13
|
+
command '' do
|
14
|
+
desc 'empty description'
|
15
|
+
end
|
16
|
+
|
17
|
+
command 'command_without_description' do
|
18
|
+
end
|
19
|
+
|
20
|
+
command 'What\'s the weather in <city>?' do
|
21
|
+
desc 'Tells you the weather in a <city>.'
|
22
|
+
long_desc "Accurate 10 Day Weather Forecasts for thousands of places around the World.\n" \
|
23
|
+
'We provide detailed Weather Forecasts over a 10 day period updated four times a day.'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class HelloCommand < SlackRubyBot::Commands::Base
|
29
|
+
help do
|
30
|
+
title 'hello'
|
31
|
+
desc 'Says hello.'
|
32
|
+
long_desc 'The long description'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
shared_examples 'a slack ruby bot' do
|
4
|
+
context 'not configured' do
|
5
|
+
before do
|
6
|
+
@slack_api_token = ENV.delete('SLACK_API_TOKEN')
|
7
|
+
SlackRubyBot.configure { |config| config.token = nil }
|
8
|
+
end
|
9
|
+
after do
|
10
|
+
ENV['SLACK_API_TOKEN'] = @slack_api_token
|
11
|
+
end
|
12
|
+
it 'requires SLACK_API_TOKEN' do
|
13
|
+
expect { described_class.instance }.to raise_error RuntimeError, 'Missing Slack API Token.'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :not_respond do
|
6
|
+
match do |actual|
|
7
|
+
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
|
8
|
+
|
9
|
+
message_command = SlackRubyBot::Hooks::Message.new
|
10
|
+
channel, user, message, attachments = parse(actual)
|
11
|
+
|
12
|
+
expect(client).not_to receive(:message)
|
13
|
+
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse(actual)
|
20
|
+
actual = { message: actual } unless actual.is_a?(Hash)
|
21
|
+
attachments = actual[:attachments]
|
22
|
+
attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
|
23
|
+
[actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :respond_with_error do |error, error_message|
|
6
|
+
match do |actual|
|
7
|
+
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
|
8
|
+
|
9
|
+
message_command = SlackRubyBot::Hooks::Message.new
|
10
|
+
channel, user, message, attachments = parse(actual)
|
11
|
+
|
12
|
+
begin
|
13
|
+
expect do
|
14
|
+
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
|
15
|
+
end.to raise_error error, error_message
|
16
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
17
|
+
@error_message = e.message
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
failure_message do |actual|
|
24
|
+
_, _, message = parse(actual)
|
25
|
+
@error_message || "expected for '#{message}' to fail with '#{expected}'"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse(actual)
|
31
|
+
actual = { message: actual } unless actual.is_a?(Hash)
|
32
|
+
attachments = actual[:attachments]
|
33
|
+
attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
|
34
|
+
[actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :respond_with_slack_message do |expected|
|
6
|
+
include SlackRubyBot::SpecHelpers
|
7
|
+
|
8
|
+
match do |actual|
|
9
|
+
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
|
10
|
+
|
11
|
+
message_command = SlackRubyBot::Hooks::Message.new
|
12
|
+
channel, user, message, attachments = parse(actual)
|
13
|
+
|
14
|
+
allow(client).to receive(:message) do |options|
|
15
|
+
@messages ||= []
|
16
|
+
@messages.push options
|
17
|
+
end
|
18
|
+
|
19
|
+
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
|
20
|
+
|
21
|
+
matcher = have_received(:message).once
|
22
|
+
matcher = matcher.with(hash_including(channel: channel, text: expected)) if channel && expected
|
23
|
+
|
24
|
+
expect(client).to matcher
|
25
|
+
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
failure_message do |_actual|
|
30
|
+
message = "expected to receive message with text: #{expected} once,\n received:"
|
31
|
+
message += @messages&.any? ? @messages.inspect : 'none'
|
32
|
+
message
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :respond_with_slack_messages do |expected|
|
6
|
+
include SlackRubyBot::SpecHelpers
|
7
|
+
|
8
|
+
match do |actual|
|
9
|
+
raise ArgumentError, 'respond_with_slack_messages expects an array of ordered responses' if expected && !expected.respond_to?(:each)
|
10
|
+
|
11
|
+
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
|
12
|
+
|
13
|
+
message_command = SlackRubyBot::Hooks::Message.new
|
14
|
+
channel, user, message, attachments = parse(actual)
|
15
|
+
|
16
|
+
@messages ||= []
|
17
|
+
allow(client).to receive(:message) do |options|
|
18
|
+
@messages.push options
|
19
|
+
end
|
20
|
+
|
21
|
+
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
|
22
|
+
|
23
|
+
@responses = []
|
24
|
+
|
25
|
+
if expected&.any?
|
26
|
+
expected.each do |exp|
|
27
|
+
@responses.push(expect(client).to(have_received(:message).with(hash_including(channel: channel, text: exp)).once))
|
28
|
+
end
|
29
|
+
else
|
30
|
+
expect(@messages.size).to be > 1
|
31
|
+
end
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
failure_message do |_actual|
|
37
|
+
if expected&.any?
|
38
|
+
expected.map do |exp|
|
39
|
+
"Expected text: #{exp}, got #{@messages[expected.index(exp)] || 'none'}" unless @responses[expected.index(exp)]
|
40
|
+
end.compact.join("\n")
|
41
|
+
else
|
42
|
+
"Expected to receive multiple messages, got #{@messages.any? ? @messages.size : 'none'}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :start_typing do |expected|
|
6
|
+
include SlackRubyBot::SpecHelpers
|
7
|
+
|
8
|
+
match do |actual|
|
9
|
+
client = respond_to?(:client) ? send(:client) : SlackRubyBot::Client.new
|
10
|
+
|
11
|
+
message_command = SlackRubyBot::Hooks::Message.new
|
12
|
+
|
13
|
+
allow(client).to receive(:typing) do |options|
|
14
|
+
@test_options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
channel, user, message, attachments = parse(actual)
|
18
|
+
message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
|
19
|
+
|
20
|
+
matcher = have_received(:typing).once
|
21
|
+
matcher = matcher.with(expected) if expected&.any?
|
22
|
+
expect(client).to matcher
|
23
|
+
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
failure_message do |_actual|
|
28
|
+
message = "expected to receive typing with: #{expected} once,\n received:"
|
29
|
+
message += @test_options&.any? ? @test_options.inspect : ' none'
|
30
|
+
message
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.before :each do
|
5
|
+
SlackRubyBot.configure do |c|
|
6
|
+
c.token = 'testtoken'
|
7
|
+
c.user = 'rubybot'
|
8
|
+
c.user_id = 'DEADBEEF'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
config.after :each do
|
13
|
+
SlackRubyBot::Config.reset!
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackRubyBot
|
4
|
+
module SpecHelpers
|
5
|
+
private
|
6
|
+
|
7
|
+
def parse(actual)
|
8
|
+
actual = { message: actual } unless actual.is_a?(Hash)
|
9
|
+
attachments = actual[:attachments]
|
10
|
+
attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
|
11
|
+
[actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rspec'
|
7
|
+
require 'rack/test'
|
8
|
+
|
9
|
+
require 'config/environment'
|
10
|
+
require 'slack-ruby-bot'
|
11
|
+
|
12
|
+
Dir[File.join(File.dirname(__FILE__), 'rspec/support', '**/*.rb')].sort.each do |file|
|
13
|
+
require file
|
14
|
+
end
|