slack_message 1.7.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2cd232d81a66a48c9543a5b910c335bca7a82474a60472d03f7cffab3fce0d0
4
- data.tar.gz: c27312311ff02ee414f2882461fe5ea9b5c07d9d8e99847ae7af33faf4a31283
3
+ metadata.gz: f4d33554e024d1c51aeb2bb6ee5397c16142dbcc1a314720f30e7fab3d12325d
4
+ data.tar.gz: 6e297c58c27ca6109d51fc5b267c3248928deaf7422d57e6b9e9aa0533e525b5
5
5
  SHA512:
6
- metadata.gz: 69352d5eb8d4f6837f66ba042817bfe0a51c3483356bbc64ded0cbc60fc7e72c084ea39df542a93bf79a6c0a8c4ad860cdb9791f28d807edb5b1c859b13f129f
7
- data.tar.gz: 740af84829c6b258b4788b5f83901f242a5732e98737781efb288ee426ed232726823d9fdde453da6c7d83eb55607733e1d4b55757580676921b37abba1c70ad
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slack_message (1.2.0)
4
+ slack_message (1.9.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
  ------------
@@ -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
- }.to_json
48
+ }
48
49
 
49
- response = Net::HTTP.post_form uri, { payload: params }
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"
@@ -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 Arguments, "please pass an array" unless elements.respond_to?(:map)
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 Arguments, "please pass an array" unless elements.respond_to?(:map)
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "1.7.0"
3
+ gem.version = "1.9.0"
4
4
  gem.summary = "A nice DSL for composing rich messages in Slack"
5
5
  gem.authors = ["Joe Mastey"]
6
6
  gem.email = 'hello@joemastey.com'
@@ -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
- allow(SlackMessage::Api).to receive(:api_request)
11
- .with(/hello@joemastey.com/)
12
- .and_return({ "user" => { "id" => "ABC123" }})
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
- #config.api_token = "abc123"
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.7.0
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-06 00:00:00.000000000 Z
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