slack-ruby-client 0.13.1 → 0.14.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 +4 -4
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +9 -32
- data/.travis.yml +4 -4
- data/CHANGELOG.md +10 -0
- data/Dangerfile +1 -0
- data/Gemfile +1 -3
- data/LICENSE.md +1 -1
- data/README.md +100 -13
- data/bin/commands.rb +1 -0
- data/bin/commands/apps.rb +14 -0
- data/bin/commands/chat.rb +5 -1
- data/bin/commands/conversations.rb +1 -0
- data/bin/commands/files.rb +8 -9
- data/bin/commands/reactions.rb +2 -2
- data/bin/slack +1 -1
- data/lib/slack-ruby-client.rb +4 -0
- data/lib/slack/events/config.rb +31 -0
- data/lib/slack/events/request.rb +60 -0
- data/lib/slack/real_time/client.rb +35 -7
- data/lib/slack/real_time/concurrency/async.rb +34 -2
- data/lib/slack/real_time/concurrency/celluloid.rb +28 -9
- data/lib/slack/real_time/concurrency/eventmachine.rb +25 -4
- data/lib/slack/real_time/socket.rb +19 -0
- data/lib/slack/real_time/stores/store.rb +2 -0
- data/lib/slack/version.rb +1 -1
- data/lib/slack/web/api/endpoints.rb +2 -0
- data/lib/slack/web/api/endpoints/apps.rb +26 -0
- data/lib/slack/web/api/endpoints/chat.rb +30 -4
- data/lib/slack/web/api/endpoints/conversations.rb +2 -0
- data/lib/slack/web/api/endpoints/files.rb +8 -9
- data/lib/slack/web/api/endpoints/reactions.rb +2 -2
- data/lib/slack/web/api/patches/chat.6.block-kit-support.patch +69 -0
- data/lib/slack/web/pagination/cursor.rb +3 -0
- data/lib/tasks/real_time.rake +2 -0
- data/lib/tasks/web.rake +1 -0
- data/slack-ruby-client.gemspec +3 -2
- data/spec/integration/integration_spec.rb +64 -6
- data/spec/slack/events/config_spec.rb +29 -0
- data/spec/slack/events/request_spec.rb +121 -0
- data/spec/slack/real_time/client_spec.rb +36 -1
- data/spec/slack/real_time/concurrency/eventmachine_spec.rb +1 -0
- data/spec/slack/web/api/endpoints/apps_spec.rb +15 -0
- data/spec/slack/web/api/endpoints/custom_specs/chat_spec.rb +45 -24
- data/spec/spec_helper.rb +1 -0
- data/spec/support/queue_with_timeout.rb +4 -4
- metadata +29 -4
@@ -82,18 +82,22 @@ module Slack
|
|
82
82
|
# @option options [user] :user
|
83
83
|
# id of the user who will receive the ephemeral message. The user should be in the channel specified by the channel argument.
|
84
84
|
# @option options [Object] :as_user
|
85
|
-
# Pass true to post the message as the authed
|
85
|
+
# Pass true to post the message as the authed user. Defaults to true if the chat:write:bot scope is not included. Otherwise, defaults to false.
|
86
86
|
# @option options [Object] :attachments
|
87
87
|
# A JSON-based array of structured attachments, presented as a URL-encoded string.
|
88
|
+
# @option options [Object] :blocks
|
89
|
+
# A JSON-based array of structured blocks, presented as a URL-encoded string.
|
88
90
|
# @option options [Object] :link_names
|
89
91
|
# Find and link channel names and usernames.
|
90
92
|
# @option options [Object] :parse
|
91
93
|
# Change how messages are treated. Defaults to none. See below.
|
94
|
+
# @option options [Object] :thread_ts
|
95
|
+
# Provide another message's ts value to make this message a reply. Avoid using a reply's ts value; use its parent instead.
|
92
96
|
# @see https://api.slack.com/methods/chat.postEphemeral
|
93
97
|
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.postEphemeral.json
|
94
98
|
def chat_postEphemeral(options = {})
|
95
99
|
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
96
|
-
throw ArgumentError.new('Required arguments :text or :
|
100
|
+
throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
97
101
|
throw ArgumentError.new('Required arguments :user missing') if options[:user].nil?
|
98
102
|
options = options.merge(user: users_id(options)['user']['id']) if options[:user]
|
99
103
|
# attachments must be passed as an encoded JSON string
|
@@ -102,6 +106,12 @@ module Slack
|
|
102
106
|
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
103
107
|
options = options.merge(attachments: attachments)
|
104
108
|
end
|
109
|
+
# blocks must be passed as an encoded JSON string
|
110
|
+
if options.key?(:blocks)
|
111
|
+
blocks = options[:blocks]
|
112
|
+
blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
113
|
+
options = options.merge(blocks: blocks)
|
114
|
+
end
|
105
115
|
post('chat.postEphemeral', options)
|
106
116
|
end
|
107
117
|
|
@@ -116,6 +126,8 @@ module Slack
|
|
116
126
|
# Pass true to post the message as the authed user, instead of as a bot. Defaults to false. See authorship below.
|
117
127
|
# @option options [Object] :attachments
|
118
128
|
# A JSON-based array of structured attachments, presented as a URL-encoded string.
|
129
|
+
# @option options [Object] :blocks
|
130
|
+
# A JSON-based array of structured blocks, presented as a URL-encoded string.
|
119
131
|
# @option options [Object] :icon_emoji
|
120
132
|
# Emoji to use as the icon for this message. Overrides icon_url. Must be used in conjunction with as_user set to false, otherwise ignored. See authorship below.
|
121
133
|
# @option options [Object] :icon_url
|
@@ -140,13 +152,19 @@ module Slack
|
|
140
152
|
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.postMessage.json
|
141
153
|
def chat_postMessage(options = {})
|
142
154
|
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
143
|
-
throw ArgumentError.new('Required arguments :text or :
|
155
|
+
throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
144
156
|
# attachments must be passed as an encoded JSON string
|
145
157
|
if options.key?(:attachments)
|
146
158
|
attachments = options[:attachments]
|
147
159
|
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
148
160
|
options = options.merge(attachments: attachments)
|
149
161
|
end
|
162
|
+
# blocks must be passed as an encoded JSON string
|
163
|
+
if options.key?(:blocks)
|
164
|
+
blocks = options[:blocks]
|
165
|
+
blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
166
|
+
options = options.merge(blocks: blocks)
|
167
|
+
end
|
150
168
|
post('chat.postMessage', options)
|
151
169
|
end
|
152
170
|
|
@@ -188,6 +206,8 @@ module Slack
|
|
188
206
|
# Pass true to update the message as the authed user. Bot users in this context are considered authed users.
|
189
207
|
# @option options [Object] :attachments
|
190
208
|
# A JSON-based array of structured attachments, presented as a URL-encoded string. This field is required when not presenting text.
|
209
|
+
# @option options [Object] :blocks
|
210
|
+
# A JSON-based array of structured blocks, presented as a URL-encoded string.
|
191
211
|
# @option options [Object] :link_names
|
192
212
|
# Find and link channel names and usernames. Defaults to none. See below.
|
193
213
|
# @option options [Object] :parse
|
@@ -196,7 +216,7 @@ module Slack
|
|
196
216
|
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.update.json
|
197
217
|
def chat_update(options = {})
|
198
218
|
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
199
|
-
throw ArgumentError.new('Required arguments :text or :
|
219
|
+
throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
200
220
|
throw ArgumentError.new('Required arguments :ts missing') if options[:ts].nil?
|
201
221
|
options = options.merge(channel: channels_id(options)['channel']['id']) if options[:channel]
|
202
222
|
# attachments must be passed as an encoded JSON string
|
@@ -205,6 +225,12 @@ module Slack
|
|
205
225
|
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
206
226
|
options = options.merge(attachments: attachments)
|
207
227
|
end
|
228
|
+
# blocks must be passed as an encoded JSON string
|
229
|
+
if options.key?(:blocks)
|
230
|
+
blocks = options[:blocks]
|
231
|
+
blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
232
|
+
options = options.merge(blocks: blocks)
|
233
|
+
end
|
208
234
|
post('chat.update', options)
|
209
235
|
end
|
210
236
|
end
|
@@ -83,6 +83,8 @@ module Slack
|
|
83
83
|
# Conversation ID to learn more about.
|
84
84
|
# @option options [Object] :include_locale
|
85
85
|
# Set this to true to receive the locale for this conversation. Defaults to false.
|
86
|
+
# @option options [Object] :include_num_members
|
87
|
+
# Set to true to include the member count for the specified conversation. Defaults to false.
|
86
88
|
# @see https://api.slack.com/methods/conversations.info
|
87
89
|
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/conversations/conversations.info.json
|
88
90
|
def conversations_info(options = {})
|
@@ -67,17 +67,16 @@ module Slack
|
|
67
67
|
# Filter files created before this timestamp (inclusive).
|
68
68
|
# @option options [Object] :types
|
69
69
|
# Filter files by type:
|
70
|
+
# * `all` - All files
|
71
|
+
# * `spaces` - Posts
|
72
|
+
# * `snippets` - Snippets
|
73
|
+
# * `images` - Image files
|
74
|
+
# * `gdocs` - Google docs
|
75
|
+
# * `zips` - Zip files
|
76
|
+
# * `pdfs` - PDF files
|
70
77
|
#
|
71
|
-
# all
|
72
|
-
# spaces - Posts
|
73
|
-
# snippets - Snippets
|
74
|
-
# images - Image files
|
75
|
-
# gdocs - Google docs
|
76
|
-
# zips - Zip files
|
77
|
-
# pdfs - PDF files
|
78
|
+
# You can pass multiple values in the types argument, like `types=spaces,snippets`.The default value is `all`, which does not filter the list.
|
78
79
|
#
|
79
|
-
#
|
80
|
-
# You can pass multiple values in the types argument, like types=spaces,snippets.The default value is all, which does not filter the list.
|
81
80
|
# .
|
82
81
|
# @option options [user] :user
|
83
82
|
# Filter files created by a single user.
|
@@ -13,9 +13,9 @@ module Slack
|
|
13
13
|
# @option options [channel] :channel
|
14
14
|
# Channel where the message to add reaction to was posted.
|
15
15
|
# @option options [file] :file
|
16
|
-
# File to add reaction to.
|
16
|
+
# File to add reaction to. Now that file threads work the way you'd expect, this argument is deprecated. Specify the timestamp and channel of the message associated with a file instead.
|
17
17
|
# @option options [Object] :file_comment
|
18
|
-
# File comment to add reaction to.
|
18
|
+
# File comment to add reaction to. Now that file threads work the way you'd expect, this argument is deprecated. Specify the timestamp and channel of the message associated with a file instead.
|
19
19
|
# @option options [Object] :timestamp
|
20
20
|
# Timestamp of the message to add reaction to.
|
21
21
|
# @see https://api.slack.com/methods/reactions.add
|
@@ -0,0 +1,69 @@
|
|
1
|
+
diff --git a/lib/slack/web/api/endpoints/chat.rb b/lib/slack/web/api/endpoints/chat.rb
|
2
|
+
index 54a7db1..c535bb5 100644
|
3
|
+
--- a/lib/slack/web/api/endpoints/chat.rb
|
4
|
+
+++ b/lib/slack/web/api/endpoints/chat.rb
|
5
|
+
@@ -97,7 +97,7 @@ module Slack
|
6
|
+
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.postEphemeral.json
|
7
|
+
def chat_postEphemeral(options = {})
|
8
|
+
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
9
|
+
- throw ArgumentError.new('Required arguments :text or :attachments missing') if options[:text].nil? && options[:attachments].nil?
|
10
|
+
+ throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
11
|
+
throw ArgumentError.new('Required arguments :user missing') if options[:user].nil?
|
12
|
+
options = options.merge(user: users_id(options)['user']['id']) if options[:user]
|
13
|
+
# attachments must be passed as an encoded JSON string
|
14
|
+
@@ -106,6 +106,12 @@ module Slack
|
15
|
+
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
16
|
+
options = options.merge(attachments: attachments)
|
17
|
+
end
|
18
|
+
+ # blocks must be passed as an encoded JSON string
|
19
|
+
+ if options.key?(:blocks)
|
20
|
+
+ blocks = options[:blocks]
|
21
|
+
+ blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
22
|
+
+ options = options.merge(blocks: blocks)
|
23
|
+
+ end
|
24
|
+
post('chat.postEphemeral', options)
|
25
|
+
end
|
26
|
+
|
27
|
+
@@ -146,13 +152,19 @@ module Slack
|
28
|
+
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.postMessage.json
|
29
|
+
def chat_postMessage(options = {})
|
30
|
+
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
31
|
+
- throw ArgumentError.new('Required arguments :text or :attachments missing') if options[:text].nil? && options[:attachments].nil?
|
32
|
+
+ throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
33
|
+
# attachments must be passed as an encoded JSON string
|
34
|
+
if options.key?(:attachments)
|
35
|
+
attachments = options[:attachments]
|
36
|
+
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
37
|
+
options = options.merge(attachments: attachments)
|
38
|
+
end
|
39
|
+
+ # blocks must be passed as an encoded JSON string
|
40
|
+
+ if options.key?(:blocks)
|
41
|
+
+ blocks = options[:blocks]
|
42
|
+
+ blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
43
|
+
+ options = options.merge(blocks: blocks)
|
44
|
+
+ end
|
45
|
+
post('chat.postMessage', options)
|
46
|
+
end
|
47
|
+
|
48
|
+
@@ -204,7 +216,7 @@ module Slack
|
49
|
+
# @see https://github.com/slack-ruby/slack-api-ref/blob/master/methods/chat/chat.update.json
|
50
|
+
def chat_update(options = {})
|
51
|
+
throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
|
52
|
+
- throw ArgumentError.new('Required arguments :text or :attachments missing') if options[:text].nil? && options[:attachments].nil?
|
53
|
+
+ throw ArgumentError.new('Required arguments :text, :attachments or :blocks missing') if options[:text].nil? && options[:attachments].nil? && options[:blocks].nil?
|
54
|
+
throw ArgumentError.new('Required arguments :ts missing') if options[:ts].nil?
|
55
|
+
options = options.merge(channel: channels_id(options)['channel']['id']) if options[:channel]
|
56
|
+
# attachments must be passed as an encoded JSON string
|
57
|
+
@@ -213,6 +225,12 @@ module Slack
|
58
|
+
attachments = JSON.dump(attachments) unless attachments.is_a?(String)
|
59
|
+
options = options.merge(attachments: attachments)
|
60
|
+
end
|
61
|
+
+ # blocks must be passed as an encoded JSON string
|
62
|
+
+ if options.key?(:blocks)
|
63
|
+
+ blocks = options[:blocks]
|
64
|
+
+ blocks = JSON.dump(blocks) unless blocks.is_a?(String)
|
65
|
+
+ options = options.merge(blocks: blocks)
|
66
|
+
+ end
|
67
|
+
post('chat.update', options)
|
68
|
+
end
|
69
|
+
end
|
@@ -28,6 +28,7 @@ module Slack
|
|
28
28
|
response = client.send(verb, query)
|
29
29
|
rescue Slack::Web::Api::Errors::TooManyRequestsError => e
|
30
30
|
raise e if retry_count >= max_retries
|
31
|
+
|
31
32
|
client.logger.debug("#{self.class}##{__method__}") { e.to_s }
|
32
33
|
retry_count += 1
|
33
34
|
sleep(e.retry_after.seconds)
|
@@ -35,8 +36,10 @@ module Slack
|
|
35
36
|
end
|
36
37
|
yield response
|
37
38
|
break unless response.response_metadata
|
39
|
+
|
38
40
|
next_cursor = response.response_metadata.next_cursor
|
39
41
|
break if next_cursor.blank?
|
42
|
+
|
40
43
|
retry_count = 0
|
41
44
|
sleep(sleep_interval) if sleep_interval
|
42
45
|
end
|
data/lib/tasks/real_time.rake
CHANGED
@@ -15,12 +15,14 @@ namespace :slack do
|
|
15
15
|
parsed = JSON.parse(File.read(path))
|
16
16
|
JSON::Validator.validate(event_schema, parsed, insert_defaults: true)
|
17
17
|
next if %w[message hello].include?(name)
|
18
|
+
|
18
19
|
result[name] = parsed
|
19
20
|
end
|
20
21
|
|
21
22
|
event_handler_template = Erubis::Eruby.new(File.read('lib/slack/real_time/api/templates/event_handler.erb'))
|
22
23
|
Dir.glob('lib/slack/real_time/stores/**/*.rb').each do |store_file|
|
23
24
|
next if File.basename(store_file) == 'base.rb'
|
25
|
+
|
24
26
|
STDOUT.write "#{File.basename(store_file)}:"
|
25
27
|
|
26
28
|
store_file_contents = File.read(store_file)
|
data/lib/tasks/web.rake
CHANGED
data/slack-ruby-client.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$LOAD_PATH.push File.expand_path('
|
1
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
2
|
require 'slack/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
@@ -26,7 +26,8 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_development_dependency 'json-schema'
|
27
27
|
s.add_development_dependency 'rake', '~> 10'
|
28
28
|
s.add_development_dependency 'rspec'
|
29
|
-
s.add_development_dependency 'rubocop', '0.
|
29
|
+
s.add_development_dependency 'rubocop', '0.61.1'
|
30
|
+
s.add_development_dependency 'timecop'
|
30
31
|
s.add_development_dependency 'vcr'
|
31
32
|
s.add_development_dependency 'webmock'
|
32
33
|
end
|
@@ -9,7 +9,7 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
9
9
|
|
10
10
|
let(:logger) do
|
11
11
|
logger = Logger.new(STDOUT)
|
12
|
-
logger.level = Logger::
|
12
|
+
logger.level = Logger::INFO
|
13
13
|
logger
|
14
14
|
end
|
15
15
|
|
@@ -19,16 +19,18 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
19
19
|
Slack.configure do |slack|
|
20
20
|
slack.logger = logger
|
21
21
|
end
|
22
|
+
|
23
|
+
@queue = QueueWithTimeout.new
|
22
24
|
end
|
23
25
|
|
24
26
|
after do
|
25
27
|
Slack.config.reset
|
26
28
|
end
|
27
29
|
|
28
|
-
let(:queue) { QueueWithTimeout.new }
|
29
|
-
|
30
30
|
let(:client) { Slack::RealTime::Client.new(token: ENV['SLACK_API_TOKEN']) }
|
31
31
|
|
32
|
+
let(:queue) { @queue }
|
33
|
+
|
32
34
|
def start
|
33
35
|
# starts the client and pushes an item on a queue when connected
|
34
36
|
client.start_async do |driver|
|
@@ -45,13 +47,14 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
45
47
|
end
|
46
48
|
|
47
49
|
client.on :close do
|
50
|
+
logger.info 'Disconnecting ...'
|
48
51
|
# pushes another item to the queue when disconnected
|
49
|
-
queue.push nil
|
52
|
+
queue.push nil if @queue
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
56
|
def start_server
|
54
|
-
dt = rand(
|
57
|
+
dt = rand(2..6)
|
55
58
|
logger.debug "#start_server, waiting #{dt} second(s)"
|
56
59
|
sleep dt # prevent Slack 429 rate limit errors
|
57
60
|
# start server and wait for on :open
|
@@ -61,9 +64,12 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
61
64
|
end
|
62
65
|
|
63
66
|
def wait_for_server
|
67
|
+
return unless @queue
|
68
|
+
|
64
69
|
logger.debug '#wait_for_server'
|
65
70
|
queue.pop_with_timeout(5)
|
66
71
|
logger.debug '#wait_for_server, joined'
|
72
|
+
@queue = nil
|
67
73
|
end
|
68
74
|
|
69
75
|
def stop_server
|
@@ -82,7 +88,7 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
82
88
|
start_server
|
83
89
|
end
|
84
90
|
|
85
|
-
let(:channel) { "@#{client.self.
|
91
|
+
let(:channel) { "@#{client.self.id}" }
|
86
92
|
|
87
93
|
it 'responds to message' do
|
88
94
|
message = SecureRandom.hex
|
@@ -91,6 +97,7 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
91
97
|
logger.debug data
|
92
98
|
# concurrent execution of tests causes messages to arrive in any order
|
93
99
|
next unless data.text == message
|
100
|
+
|
94
101
|
expect(data.text).to eq message
|
95
102
|
expect(data.subtype).to eq 'bot_message'
|
96
103
|
logger.debug 'client.stop!'
|
@@ -118,6 +125,57 @@ RSpec.describe 'integration test', skip: (!ENV['SLACK_API_TOKEN'] || !ENV['CONCU
|
|
118
125
|
start_server
|
119
126
|
end
|
120
127
|
|
128
|
+
context 'with websocket_ping set' do
|
129
|
+
before do
|
130
|
+
client.websocket_ping = 2
|
131
|
+
end
|
132
|
+
it 'sends pings' do
|
133
|
+
@reply_to = nil
|
134
|
+
client.on :pong do |data|
|
135
|
+
@reply_to = data.reply_to
|
136
|
+
queue.push nil
|
137
|
+
client.stop!
|
138
|
+
end
|
139
|
+
start_server
|
140
|
+
queue.pop_with_timeout(5)
|
141
|
+
expect(@reply_to).to be 1
|
142
|
+
end
|
143
|
+
it 'rebuilds the websocket connection when dropped' do
|
144
|
+
@reply_to = nil
|
145
|
+
client.on :pong do |data|
|
146
|
+
@reply_to = data.reply_to
|
147
|
+
if @reply_to == 1
|
148
|
+
client.instance_variable_get(:@socket).close
|
149
|
+
else
|
150
|
+
expect(@reply_to).to be 2
|
151
|
+
queue.push nil
|
152
|
+
client.stop!
|
153
|
+
end
|
154
|
+
end
|
155
|
+
start_server
|
156
|
+
queue.pop_with_timeout(10)
|
157
|
+
queue.pop_with_timeout(10)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'with websocket_ping not set' do
|
162
|
+
before do
|
163
|
+
client.websocket_ping = 0
|
164
|
+
end
|
165
|
+
it 'does not send pings' do
|
166
|
+
@reply_to = nil
|
167
|
+
client.on :pong do |data|
|
168
|
+
@reply_to = data.reply_to
|
169
|
+
end
|
170
|
+
client.on :hello do
|
171
|
+
client.stop!
|
172
|
+
end
|
173
|
+
start_server
|
174
|
+
wait_for_server
|
175
|
+
expect(@reply_to).to be nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
121
179
|
it 'gets close, followed by closed' do
|
122
180
|
client.on :hello do
|
123
181
|
expect(client.started?).to be true
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Slack::Events::Config do
|
4
|
+
before do
|
5
|
+
ENV['SLACK_SIGNING_SECRET'] = 'secret'
|
6
|
+
Slack::Events::Config.reset
|
7
|
+
end
|
8
|
+
it 'defaults signing secret to ENV[SLACK_SIGNING_SECRET]' do
|
9
|
+
expect(Slack::Events.config.signing_secret).to eq 'secret'
|
10
|
+
end
|
11
|
+
it 'defaults signature expiration to 5 minutes' do
|
12
|
+
expect(Slack::Events.config.signature_expires_in).to eq 5 * 60
|
13
|
+
end
|
14
|
+
context 'configured' do
|
15
|
+
before do
|
16
|
+
Slack::Events.configure do |config|
|
17
|
+
config.signing_secret = 'custom'
|
18
|
+
config.signature_expires_in = 45
|
19
|
+
end
|
20
|
+
end
|
21
|
+
it 'uses the configured values' do
|
22
|
+
expect(Slack::Events.config.signing_secret).to eq 'custom'
|
23
|
+
expect(Slack::Events.config.signature_expires_in).to eq 45
|
24
|
+
end
|
25
|
+
end
|
26
|
+
after do
|
27
|
+
ENV.delete 'SLACK_SIGNING_SECRET'
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Slack::Events::Request do
|
4
|
+
before do
|
5
|
+
Slack::Events.configure do |config|
|
6
|
+
config.signing_secret = 'ade6ca762ade4db0e7d31484cd616b9c'
|
7
|
+
config.signature_expires_in = 30
|
8
|
+
end
|
9
|
+
end
|
10
|
+
let(:signature) { 'v0=91177eea054d65de0fc0f9b4ec57714307bc0ce2c5f3bf0d28b1b720c8f92ba2' }
|
11
|
+
let(:timestamp) { '1547933148' }
|
12
|
+
let(:body) { '{"token":"X34FAqCu8tmGEkEEpoDncnja","challenge":"P7sFXA4o3HV2hTx4zb4zcQ9yrvuQs8pDh6EacOxmMRj0tJaXfQFF","type":"url_verification"}' }
|
13
|
+
let(:http_request) do
|
14
|
+
double(
|
15
|
+
headers: {
|
16
|
+
'X-Slack-Request-Timestamp' => timestamp,
|
17
|
+
'X-Slack-Signature' => signature
|
18
|
+
},
|
19
|
+
body: double(
|
20
|
+
read: body
|
21
|
+
)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
subject do
|
25
|
+
Slack::Events::Request.new(http_request)
|
26
|
+
end
|
27
|
+
it 'reads http request' do
|
28
|
+
expect(subject.signature).to eq signature
|
29
|
+
expect(subject.body).to eq body
|
30
|
+
expect(subject.timestamp).to eq timestamp
|
31
|
+
expect(subject.version).to eq 'v0'
|
32
|
+
end
|
33
|
+
context 'time' do
|
34
|
+
after do
|
35
|
+
Timecop.return
|
36
|
+
end
|
37
|
+
context 'with an invalid signature' do
|
38
|
+
let(:signature) { 'v0=invalid' }
|
39
|
+
before do
|
40
|
+
Timecop.freeze(Time.at(timestamp.to_i))
|
41
|
+
end
|
42
|
+
it 'is invalid but not expired' do
|
43
|
+
expect(subject).to_not be_valid
|
44
|
+
expect(subject).to_not be_expired
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context 'with an invalid body' do
|
48
|
+
let(:body) { 'invalid' }
|
49
|
+
before do
|
50
|
+
Timecop.freeze(Time.at(timestamp.to_i))
|
51
|
+
end
|
52
|
+
it 'is invalid but not expired' do
|
53
|
+
expect(subject).to_not be_valid
|
54
|
+
expect(subject).to_not be_expired
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context 'with an invalid signing secret' do
|
58
|
+
before do
|
59
|
+
Slack::Events.configure do |config|
|
60
|
+
config.signing_secret = 'invalid'
|
61
|
+
end
|
62
|
+
Timecop.freeze(Time.at(timestamp.to_i))
|
63
|
+
end
|
64
|
+
it 'is invalid but not expired' do
|
65
|
+
expect(subject).to_not be_valid
|
66
|
+
expect(subject).to_not be_expired
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'within time window' do
|
70
|
+
before do
|
71
|
+
Timecop.freeze(Time.at(timestamp.to_i) + Slack::Events.config.signature_expires_in - 1)
|
72
|
+
end
|
73
|
+
it 'is valid' do
|
74
|
+
expect(subject).to be_valid
|
75
|
+
expect(subject).to_not be_expired
|
76
|
+
end
|
77
|
+
it 'does not raise an error and returns true' do
|
78
|
+
expect(subject.verify!).to be true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
context 'after time window' do
|
82
|
+
before do
|
83
|
+
Timecop.freeze(Time.at(timestamp.to_i) + Slack::Events.config.signature_expires_in + 1)
|
84
|
+
end
|
85
|
+
it 'is valid but expired' do
|
86
|
+
expect(subject).to be_valid
|
87
|
+
expect(subject).to be_expired
|
88
|
+
end
|
89
|
+
it 'raises an error on verify!' do
|
90
|
+
expect { subject.verify! }.to raise_error Slack::Events::Request::TimestampExpired
|
91
|
+
end
|
92
|
+
end
|
93
|
+
context 'before time but within window' do
|
94
|
+
before do
|
95
|
+
Timecop.freeze(Time.at(timestamp.to_i) - Slack::Events.config.signature_expires_in + 1)
|
96
|
+
end
|
97
|
+
it 'is valid and not expired' do
|
98
|
+
expect(subject).to be_valid
|
99
|
+
expect(subject).to_not be_expired
|
100
|
+
end
|
101
|
+
it 'does not raise an error on verify!' do
|
102
|
+
expect(subject.verify!).to be true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
context 'before time window' do
|
106
|
+
before do
|
107
|
+
Timecop.freeze(Time.at(timestamp.to_i) - Slack::Events.config.signature_expires_in - 1)
|
108
|
+
end
|
109
|
+
it 'is valid but expired' do
|
110
|
+
expect(subject).to be_valid
|
111
|
+
expect(subject).to be_expired
|
112
|
+
end
|
113
|
+
it 'raises an error on verify!' do
|
114
|
+
expect { subject.verify! }.to raise_error Slack::Events::Request::TimestampExpired
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
after do
|
119
|
+
Slack::Events.config.reset
|
120
|
+
end
|
121
|
+
end
|