slackrb 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|