slack_message 1.7.0 → 1.9.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/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +44 -0
- data/lib/slack_message/api.rb +7 -2
- data/lib/slack_message/configuration.rb +4 -0
- data/lib/slack_message/dsl.rb +17 -3
- data/lib/slack_message/rspec.rb +168 -0
- data/slack_message.gemspec +1 -1
- data/spec/slack_message_spec.rb +56 -9
- data/spec/spec_helper.rb +3 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4d33554e024d1c51aeb2bb6ee5397c16142dbcc1a314720f30e7fab3d12325d
|
4
|
+
data.tar.gz: 6e297c58c27ca6109d51fc5b267c3248928deaf7422d57e6b9e9aa0533e525b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf1e3caebf3d58c238c46be5f3b62a925ade7d077998a7a73ac1c9fdb3d5f0859ecb6c0f338ad2cc666a7157d166cd8cda4fa91e9e5dacb3a77bf168cb9ccf50
|
7
|
+
data.tar.gz: '08de1a2fe10e3da489169e11d478e7afb93fd557e6da00d1af1a42f6697a351f3b281ea66aafd80594738f6b25457fdb18c9a5bf766caee339d2b7cc5db51e02'
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,23 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.9.0] - 2021-10-27
|
6
|
+
- Add many validations so that trying to add e.g. empty text won't succeed.
|
7
|
+
Previously that would be accepted but return `invalid_blocks` from the API.
|
8
|
+
|
9
|
+
## [1.8.1] - 2021-10-08
|
10
|
+
- Cleaned that rspec code a bit, added more matchers for real world use.
|
11
|
+
|
12
|
+
## [1.8.0] - 2021-10-07
|
13
|
+
- Added the ability to test in RSpec.
|
14
|
+
|
15
|
+
## [1.7.1] - 2021-10-06
|
16
|
+
- Fixed literally a syntax issue.
|
17
|
+
- Fixed specs.
|
18
|
+
- Fixed API to include JSON since consumers may not have loaded it.
|
19
|
+
|
5
20
|
## [1.7.0] - 2021-10-06
|
21
|
+
- THIS RELEASE IS BADLY BROKEN.
|
6
22
|
- Added new error messages when API configuration is wrong / missing.
|
7
23
|
- Fixed issue with `instance_eval` and using methods within block.
|
8
24
|
- Fixed issue with sectionless `list_item`.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -214,6 +214,48 @@ SlackMessage.post_to('#general') do
|
|
214
214
|
end
|
215
215
|
```
|
216
216
|
|
217
|
+
### Testing
|
218
|
+
|
219
|
+
You can do some basic testing against SlackMessage, at least if you use RSpec!
|
220
|
+
You'll need to require and include the testing behavior like this, in your
|
221
|
+
spec_helper file:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
require 'slack_message/rspec'
|
225
|
+
|
226
|
+
RSpec.configure do |config|
|
227
|
+
include SlackMessage::RSpec
|
228
|
+
|
229
|
+
# your other config
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
This will stop API calls for posting messages, and will allow you access to
|
234
|
+
some custom matchers:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
expect {
|
238
|
+
SlackMessage.post_to('#general') { text "foo" }
|
239
|
+
}.to post_slack_message_to('#general').with_content_matching(/foo/)
|
240
|
+
|
241
|
+
expect {
|
242
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
243
|
+
}.to post_slack_message_as(:schmoebot)
|
244
|
+
|
245
|
+
expect {
|
246
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
247
|
+
}.to post_slack_message_as('Schmoe Bot')
|
248
|
+
|
249
|
+
expect {
|
250
|
+
SlackMessage.post_to('#general') { text "foo" }
|
251
|
+
}.to post_to_slack
|
252
|
+
```
|
253
|
+
|
254
|
+
Be forewarned, I'm frankly not that great at more complicated RSpec matchers,
|
255
|
+
so I'm guessing there are some bugs. Also, because the content of a message
|
256
|
+
gets turned into a complex JSON object, matching against content isn't capable
|
257
|
+
of very complicated regexes.
|
258
|
+
|
217
259
|
Opinionated Stances
|
218
260
|
------------
|
219
261
|
|
@@ -225,6 +267,8 @@ opinionated stances on how to make use of that API. For instance:
|
|
225
267
|
* Generally, same goes for the `emoji` flag on almost every text element.
|
226
268
|
* It's possible to ask for a `blank_line` in sections, even though that concept
|
227
269
|
isn't real. In this case, a text line containing only an emspace is rendered.
|
270
|
+
* It's easy to configure a bot for consistent name / channel use. My previous
|
271
|
+
use of SlackNotifier led to frequently inconsistent names.
|
228
272
|
|
229
273
|
What it Doesn't Do
|
230
274
|
------------
|
data/lib/slack_message/api.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'net/https'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
class SlackMessage::Api
|
5
6
|
def self.user_id_for(email)
|
@@ -44,9 +45,9 @@ class SlackMessage::Api
|
|
44
45
|
channel: target,
|
45
46
|
username: profile[:name],
|
46
47
|
blocks: payload
|
47
|
-
}
|
48
|
+
}
|
48
49
|
|
49
|
-
response =
|
50
|
+
response = execute_post_form(uri, params, profile[:handle])
|
50
51
|
|
51
52
|
# let's try to be helpful about error messages
|
52
53
|
if response.body == "invalid_token"
|
@@ -63,4 +64,8 @@ class SlackMessage::Api
|
|
63
64
|
|
64
65
|
response
|
65
66
|
end
|
67
|
+
|
68
|
+
def self.execute_post_form(uri, params, _profile)
|
69
|
+
Net::HTTP.post_form uri, { payload: params.to_json }
|
70
|
+
end
|
66
71
|
end
|
@@ -27,6 +27,10 @@ module SlackMessage::Configuration
|
|
27
27
|
|
28
28
|
###
|
29
29
|
|
30
|
+
def self.clear_profiles! # test harness, mainly
|
31
|
+
@@profiles = {}
|
32
|
+
end
|
33
|
+
|
30
34
|
def self.add_profile(handle = :default, name:, url:, default_channel: nil)
|
31
35
|
if @@profiles.include?(handle)
|
32
36
|
warn "WARNING: Overriding profile '#{handle}' in SlackMessage config"
|
data/lib/slack_message/dsl.rb
CHANGED
@@ -6,7 +6,7 @@ class SlackMessage::Dsl
|
|
6
6
|
def initialize(block)
|
7
7
|
# Delegate missing methods to caller scope. Thanks 2008:
|
8
8
|
# https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
|
9
|
-
@caller_self = eval("self", block.binding
|
9
|
+
@caller_self = eval("self", block.binding)
|
10
10
|
|
11
11
|
@body = []
|
12
12
|
@default_section = Section.new
|
@@ -52,6 +52,10 @@ class SlackMessage::Dsl
|
|
52
52
|
def context(text)
|
53
53
|
finalize_default_section
|
54
54
|
|
55
|
+
if text == "" || text.nil?
|
56
|
+
raise ArgumentError, "tried to create a context block without a value"
|
57
|
+
end
|
58
|
+
|
55
59
|
@body.push({ type: "context", elements: [{
|
56
60
|
type: "mrkdwn", text: text
|
57
61
|
}]})
|
@@ -110,6 +114,10 @@ class SlackMessage::Dsl
|
|
110
114
|
end
|
111
115
|
|
112
116
|
def text(msg)
|
117
|
+
if msg == "" || msg.nil?
|
118
|
+
raise ArgumentError, "tried to create text node without a value"
|
119
|
+
end
|
120
|
+
|
113
121
|
if @body.include?(:text)
|
114
122
|
@body[:text][:text] << "\n#{msg}"
|
115
123
|
|
@@ -119,14 +127,16 @@ class SlackMessage::Dsl
|
|
119
127
|
end
|
120
128
|
|
121
129
|
def ul(elements)
|
122
|
-
raise
|
130
|
+
raise ArgumentError, "please pass an array" unless elements.respond_to?(:map)
|
131
|
+
|
123
132
|
text(
|
124
133
|
elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
|
125
134
|
)
|
126
135
|
end
|
127
136
|
|
128
137
|
def ol(elements)
|
129
|
-
raise
|
138
|
+
raise ArgumentError, "please pass an array" unless elements.respond_to?(:map)
|
139
|
+
|
130
140
|
text(
|
131
141
|
elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
|
132
142
|
)
|
@@ -186,6 +196,10 @@ class SlackMessage::Dsl
|
|
186
196
|
end
|
187
197
|
|
188
198
|
def list_item(title, value)
|
199
|
+
if value == "" || value.nil?
|
200
|
+
raise ArgumentError, "can't create a list item for '#{title}' without a value"
|
201
|
+
end
|
202
|
+
|
189
203
|
@list.add(title, value)
|
190
204
|
end
|
191
205
|
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
require 'rspec/mocks'
|
3
|
+
|
4
|
+
# Honestly, this code is what happens when you do not understand the RSpec
|
5
|
+
# custom expectation API really at all, but you really want to create your
|
6
|
+
# matcher. This code is soo baaad.
|
7
|
+
#
|
8
|
+
# We override API calls by entirely replacing the low-level API method. Then we
|
9
|
+
# use our overridden version to capture and record calls. When someone creates
|
10
|
+
# a new expectation, an object is created, so we allow that object to register
|
11
|
+
# itself to receive notification when a slack message _would have_ been posted.
|
12
|
+
#
|
13
|
+
# Then once the expectation is fulfilled, that class unregisters itself so that
|
14
|
+
# it can be cleaned up properly.
|
15
|
+
#
|
16
|
+
|
17
|
+
module SlackMessage::RSpec
|
18
|
+
extend RSpec::Matchers::DSL
|
19
|
+
|
20
|
+
@@listeners = []
|
21
|
+
|
22
|
+
def self.register_expectation_listener(expectation_instance)
|
23
|
+
@@listeners << expectation_instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.unregister_expectation_listener(expectation_instance)
|
27
|
+
@@listeners.delete(expectation_instance)
|
28
|
+
end
|
29
|
+
|
30
|
+
FauxResponse = Struct.new(:code, :body)
|
31
|
+
|
32
|
+
def self.included(_)
|
33
|
+
SlackMessage::Api.singleton_class.undef_method(:execute_post_form)
|
34
|
+
SlackMessage::Api.define_singleton_method(:execute_post_form) do |uri, params, profile|
|
35
|
+
@@listeners.each do |listener|
|
36
|
+
listener.record_call(params.merge(profile: profile, uri: uri))
|
37
|
+
end
|
38
|
+
|
39
|
+
return FauxResponse.new('200', 'ok')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# w/ channel
|
44
|
+
matcher :post_slack_message_to do |expected|
|
45
|
+
match do |actual|
|
46
|
+
@instance ||= PostTo.new
|
47
|
+
@instance.with_channel(expected)
|
48
|
+
|
49
|
+
actual.call
|
50
|
+
@instance.enforce_expectations
|
51
|
+
end
|
52
|
+
|
53
|
+
chain :with_content_matching do |content|
|
54
|
+
@instance ||= PostTo.new
|
55
|
+
@instance.with_content_matching(content)
|
56
|
+
end
|
57
|
+
|
58
|
+
failure_message { @instance.failure_message }
|
59
|
+
failure_message_when_negated { @instance.failure_message_when_negated }
|
60
|
+
|
61
|
+
supports_block_expectations
|
62
|
+
end
|
63
|
+
|
64
|
+
# no channel
|
65
|
+
matcher :post_to_slack do |expected|
|
66
|
+
match do |actual|
|
67
|
+
@instance ||= PostTo.new
|
68
|
+
|
69
|
+
actual.call
|
70
|
+
@instance.enforce_expectations
|
71
|
+
end
|
72
|
+
|
73
|
+
chain :with_content_matching do |content|
|
74
|
+
@instance ||= PostTo.new
|
75
|
+
@instance.with_content_matching(content)
|
76
|
+
end
|
77
|
+
|
78
|
+
failure_message { @instance.failure_message }
|
79
|
+
failure_message_when_negated { @instance.failure_message_when_negated }
|
80
|
+
|
81
|
+
supports_block_expectations
|
82
|
+
end
|
83
|
+
|
84
|
+
# name / profile matcher
|
85
|
+
matcher :post_slack_message_as do |expected|
|
86
|
+
match do |actual|
|
87
|
+
@instance ||= PostTo.new
|
88
|
+
@instance.with_profile(expected)
|
89
|
+
|
90
|
+
actual.call
|
91
|
+
@instance.enforce_expectations
|
92
|
+
end
|
93
|
+
|
94
|
+
chain :with_content_matching do |content|
|
95
|
+
@instance ||= PostTo.new
|
96
|
+
@instance.with_content_matching(content)
|
97
|
+
end
|
98
|
+
|
99
|
+
failure_message { @instance.failure_message }
|
100
|
+
failure_message_when_negated { @instance.failure_message_when_negated }
|
101
|
+
|
102
|
+
supports_block_expectations
|
103
|
+
end
|
104
|
+
|
105
|
+
class PostTo
|
106
|
+
def initialize
|
107
|
+
@captured_calls = []
|
108
|
+
@content = nil
|
109
|
+
@channel = nil
|
110
|
+
@profile = nil
|
111
|
+
|
112
|
+
SlackMessage::RSpec.register_expectation_listener(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
def record_call(deets)
|
116
|
+
@captured_calls.push(deets)
|
117
|
+
end
|
118
|
+
|
119
|
+
def with_channel(channel)
|
120
|
+
@channel = channel
|
121
|
+
end
|
122
|
+
|
123
|
+
def with_content_matching(content)
|
124
|
+
raise ArgumentError unless content.is_a? Regexp
|
125
|
+
@content = content
|
126
|
+
end
|
127
|
+
|
128
|
+
def with_profile(profile)
|
129
|
+
@profile = profile
|
130
|
+
end
|
131
|
+
|
132
|
+
def enforce_expectations
|
133
|
+
SlackMessage::RSpec.unregister_expectation_listener(self)
|
134
|
+
|
135
|
+
@captured_calls
|
136
|
+
.filter { |call| !@channel || call[:channel] == @channel }
|
137
|
+
.filter { |call| !@profile || [call[:profile], call[:username]].include?(@profile) }
|
138
|
+
.filter { |call| !@content || call.fetch(:blocks).to_s =~ @content }
|
139
|
+
.any?
|
140
|
+
end
|
141
|
+
|
142
|
+
def failure_message
|
143
|
+
"expected block to #{failure_expression}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def failure_message_when_negated
|
147
|
+
"expected block not to #{failure_expression}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def failure_expression
|
151
|
+
concat = []
|
152
|
+
|
153
|
+
if @channel
|
154
|
+
concat << "post a slack message to '#{@channel}'"
|
155
|
+
elsif @profile
|
156
|
+
concat << "post a slack message as '#{@profile}'"
|
157
|
+
else
|
158
|
+
concat << "post a slack message"
|
159
|
+
end
|
160
|
+
|
161
|
+
if @content
|
162
|
+
concat << "with content matching #{@content.inspect}"
|
163
|
+
end
|
164
|
+
|
165
|
+
concat.join " "
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/slack_message.gemspec
CHANGED
data/spec/slack_message_spec.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe SlackMessage do
|
4
|
-
it "includes a bunch of stuff" do
|
5
|
-
expect(SlackMessage).to respond_to(:post_to)
|
6
|
-
end
|
7
|
-
|
8
4
|
describe "API convenience" do
|
9
5
|
it "can grab user IDs" do
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
SlackMessage.configure { |c| c.api_token = "asdf" }
|
7
|
+
allow(Net::HTTP).to receive(:start).and_return(
|
8
|
+
double(code: "200", body: '{ "user": { "id": "ABC123" }}')
|
9
|
+
)
|
13
10
|
|
14
11
|
result = SlackMessage.user_id_for("hello@joemastey.com")
|
15
12
|
expect(result).to eq("ABC123")
|
@@ -49,7 +46,7 @@ RSpec.describe SlackMessage do
|
|
49
46
|
|
50
47
|
it "raises errors for missing configuration" do
|
51
48
|
SlackMessage.configure do |config|
|
52
|
-
|
49
|
+
config.api_token = nil
|
53
50
|
end
|
54
51
|
|
55
52
|
expect {
|
@@ -63,7 +60,6 @@ RSpec.describe SlackMessage do
|
|
63
60
|
config.add_profile(:nonstandard, name: 'another profile', url: 'http://hooks.slack.com/1234/')
|
64
61
|
end
|
65
62
|
|
66
|
-
|
67
63
|
expect(SlackMessage.configuration.profile(:default)[:name]).to eq('default profile')
|
68
64
|
expect(SlackMessage.configuration.profile(:nonstandard)[:name]).to eq('another profile')
|
69
65
|
|
@@ -72,4 +68,55 @@ RSpec.describe SlackMessage do
|
|
72
68
|
}.to raise_error(ArgumentError)
|
73
69
|
end
|
74
70
|
end
|
71
|
+
|
72
|
+
describe "custom expectations" do
|
73
|
+
before do
|
74
|
+
SlackMessage.configure do |config|
|
75
|
+
config.clear_profiles!
|
76
|
+
config.add_profile(name: 'default profile', url: 'http://hooks.slack.com/1234/')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can assert expectations against posts" do
|
81
|
+
expect {
|
82
|
+
SlackMessage.post_to('#lieutenant') { text "foo" }
|
83
|
+
}.not_to post_slack_message_to('#general')
|
84
|
+
|
85
|
+
expect {
|
86
|
+
SlackMessage.post_to('#general') { text "foo" }
|
87
|
+
}.to post_slack_message_to('#general').with_content_matching(/foo/)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "resets state properly" do
|
91
|
+
expect {
|
92
|
+
SlackMessage.post_to('#general') { text "foo" }
|
93
|
+
}.to post_slack_message_to('#general')
|
94
|
+
|
95
|
+
expect { }.not_to post_slack_message_to('#general')
|
96
|
+
end
|
97
|
+
|
98
|
+
it "lets you assert by profile name" do
|
99
|
+
SlackMessage.configure do |config|
|
100
|
+
config.add_profile(:schmoebot, name: 'Schmoe', url: 'http://hooks.slack.com/1234/', default_channel: '#schmoes')
|
101
|
+
end
|
102
|
+
|
103
|
+
expect {
|
104
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
105
|
+
}.to post_slack_message_to('#schmoes')
|
106
|
+
|
107
|
+
expect {
|
108
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
109
|
+
}.to post_slack_message_as(:schmoebot)
|
110
|
+
|
111
|
+
expect {
|
112
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
113
|
+
}.to post_slack_message_as('Schmoe').with_content_matching(/foo/)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can assert more generally too tbh" do
|
117
|
+
expect {
|
118
|
+
SlackMessage.post_to('#general') { text "foo" }
|
119
|
+
}.to post_to_slack.with_content_matching(/foo/)
|
120
|
+
end
|
121
|
+
end
|
75
122
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative '../lib/slack_message'
|
2
|
+
require_relative '../lib/slack_message/rspec'
|
2
3
|
|
3
4
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
5
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
@@ -49,4 +50,6 @@ RSpec.configure do |config|
|
|
49
50
|
# test failures related to randomization by passing the same `--seed` value
|
50
51
|
# as the one that triggered the failure.
|
51
52
|
Kernel.srand config.seed
|
53
|
+
|
54
|
+
include SlackMessage::RSpec
|
52
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slack_message
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Mastey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/slack_message/api.rb
|
71
71
|
- lib/slack_message/configuration.rb
|
72
72
|
- lib/slack_message/dsl.rb
|
73
|
+
- lib/slack_message/rspec.rb
|
73
74
|
- slack_message.gemspec
|
74
75
|
- spec/slack_message_spec.rb
|
75
76
|
- spec/spec_helper.rb
|