boty 0.0.17.1 → 0.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +559 -54
  4. data/bin/bot +10 -13
  5. data/docs/images/readme-01-screen-integration.png +0 -0
  6. data/docs/images/readme-02-screen-integration.png +0 -0
  7. data/docs/images/readme-03-screen-integration.png +0 -0
  8. data/lib/boty/action.rb +1 -1
  9. data/lib/boty/bot.rb +46 -63
  10. data/lib/boty/dsl.rb +52 -0
  11. data/lib/boty/eventable.rb +41 -0
  12. data/lib/boty/http.rb +33 -0
  13. data/lib/boty/locale.rb +17 -4
  14. data/lib/boty/logger.rb +21 -4
  15. data/lib/boty/rspec.rb +2 -3
  16. data/lib/boty/script_loader.rb +1 -1
  17. data/lib/boty/session.rb +21 -17
  18. data/lib/boty/slack/chat.rb +2 -2
  19. data/lib/boty/slack/message.rb +29 -0
  20. data/lib/boty/slack/users.rb +13 -0
  21. data/lib/boty/slack.rb +6 -0
  22. data/lib/boty/version.rb +1 -1
  23. data/lib/boty.rb +4 -4
  24. data/spec/boty/bot_spec.rb +105 -174
  25. data/spec/boty/dsl_spec.rb +125 -0
  26. data/spec/boty/http_spec.rb +5 -0
  27. data/spec/boty/logger_spec.rb +33 -0
  28. data/spec/boty/rspec_spec.rb +1 -1
  29. data/spec/boty/script_loader_spec.rb +27 -0
  30. data/spec/boty/session_spec.rb +9 -11
  31. data/spec/boty/slack/message_spec.rb +34 -0
  32. data/spec/boty/slack/users_spec.rb +41 -15
  33. data/spec/happy_path_spec.rb +22 -12
  34. data/spec/script/i18n_spec.rb +10 -4
  35. data/spec/script/pug_spec.rb +1 -1
  36. data/spec/spec_helper.rb +5 -2
  37. data/spec/support/logger_support.rb +20 -0
  38. data/spec/support/session_support.rb +2 -2
  39. data/template/project/bot.tt +4 -13
  40. data/template/project/script/ping.rb +3 -3
  41. metadata +14 -5
  42. data/lib/boty/message.rb +0 -27
  43. data/lib/boty/script_dsl.rb +0 -80
  44. data/spec/boty/message_spec.rb +0 -32
@@ -1,85 +1,6 @@
1
1
  module Boty
2
- RSpec.describe "happy brighty shiny path", :session do
3
- before do
4
- start_session
5
- end
6
-
7
- let(:user_id) { "U5678" }
8
-
9
- before do
10
- bot.hear(/jeeba/i) do
11
- say "Ohay <@#{user.id}>! I'm here, that's for sure."
12
- end
13
-
14
- bot.hear(/anybody there\?/i) do
15
- say "Me! Here."
16
- end
17
-
18
- bot.match(/with match/i) do
19
- say "Ohay <@#{user.id}>! It matched."
20
- end
21
-
22
- bot.command(/do stuff/i) do
23
- say "Doing some stuff"
24
- end
25
-
26
- bot.command(/exec command/i) do
27
- say "Command issued."
28
- end
29
- end
30
-
31
- it "responds to a message" do
32
- expect(bot).to receive(:say).
33
- with "Ohay <@U5678>! I'm here, that's for sure."
34
- faye.message "jeeba, are you there?", user: "U5678"
35
- end
36
-
37
- it "responds to a match" do
38
- expect(bot).to receive(:say).
39
- with "Ohay <@U5678>! It matched."
40
- faye.message "called with match?", user: "U5678"
41
- end
42
-
43
- it "does not respond to a message not binded" do
44
- expect(bot).to_not receive(:say)
45
- faye.message "are you there?"
46
- end
47
-
48
- it "understands #hear as a message binding" do
49
- expect(bot).to receive(:say).with("Me! Here.")
50
- faye.message "anybody there?"
51
- end
52
-
53
- context "Direct message" do
54
- it "responds when mentioned by name" do
55
- expect(bot).to receive(:say).with "Doing some stuff"
56
- faye.message "<@jeeba>: do stuff!"
57
- end
58
-
59
- it "responds when mentioned by id" do
60
- expect(bot).to receive(:say).with "Doing some stuff"
61
- faye.message "<@U1234>: do stuff!"
62
- end
63
-
64
- it "responds just once" do
65
- expect(bot).to receive(:im).once
66
- faye.message "<@U1234>: knows"
67
- end
68
-
69
- it "responds when mentioned by both name and id in the same message" do
70
- expect(bot).to receive(:im).once
71
- faye.message "<@U1234|jabber>: knows"
72
- end
73
-
74
- it "understands `#command` bindings" do
75
- expect(bot).to receive(:say).with "Command issued."
76
- faye.message "<@U1234>: exec command xpto"
77
- end
78
- end
79
- end
80
-
81
2
  RSpec.describe Bot do
82
- subject(:bot) { described_class.new bot_info, Session.new }
3
+ subject(:bot) { described_class.new bot_info }
83
4
 
84
5
  let(:bot_info) {{
85
6
  "id" => "666",
@@ -138,7 +59,10 @@ module Boty
138
59
  end
139
60
 
140
61
  describe "#match", :users do
141
- let(:data) { {"type" => "message", "text" => "bbq omg lol"} }
62
+ let(:data) {{
63
+ "type" => "message",
64
+ "text" => "bbq omg lol"
65
+ }}
142
66
 
143
67
  it "binds a regex to events of `type => message`" do
144
68
  _message = nil
@@ -147,7 +71,7 @@ module Boty
147
71
  end
148
72
  bot.event data
149
73
 
150
- expect(_message).to be_a Message
74
+ expect(_message).to be_a Boty::Slack::Message
151
75
  expect(_message.text).to eq "bbq omg lol"
152
76
  expect(_message.match[0]).to eq "omg"
153
77
  end
@@ -171,6 +95,14 @@ module Boty
171
95
  expect(_lol).to eq "lol"
172
96
  expect(_bbq).to eq "bbq"
173
97
  end
98
+
99
+ it "evals the block in the context of the bot" do
100
+ dsl = nil
101
+ bot.match(/omg/) { dsl = self }
102
+ bot.event data
103
+
104
+ expect(dsl.bot).to eq bot
105
+ end
174
106
  end
175
107
 
176
108
  describe "#respond", :users do
@@ -226,20 +158,23 @@ module Boty
226
158
  start_session
227
159
  end
228
160
 
229
- describe "#no_message" do
230
- let(:data) { {"type" => "message", "text" => "bbq omg lol"} }
161
+ describe "#no_match" do
162
+ let(:data) {{
163
+ "type" => "message",
164
+ "text" => "bbq omg lol"
165
+ }}
231
166
 
232
167
  it "removes a message based on the regex AND the block" do
233
168
  permanent_executed = false
234
169
  permanent_block = ->() { permanent_executed = true }
235
- bot.hear(/omg/i, &permanent_block)
170
+ bot.match(/omg/i, &permanent_block)
236
171
 
237
172
  ephemeral_executed = false
238
173
  ephemeral_block = ->() { ephemeral_executed = true }
239
- bot.hear(/omg/i, &ephemeral_block)
174
+ bot.match(/omg/i, &ephemeral_block)
240
175
  bot.no_match(/omg/i, &ephemeral_block)
241
176
 
242
- dsl.bot.event data
177
+ bot.event data
243
178
 
244
179
  expect(permanent_executed).to eq true
245
180
  expect(ephemeral_executed).to eq false
@@ -247,11 +182,11 @@ module Boty
247
182
 
248
183
  it "removes all binds for a specific regex" do
249
184
  first = second = false
250
- bot.hear(/omg/i) { first = true }
251
- bot.hear(/omg/i) { second = true }
185
+ bot.match(/omg/i) { first = true }
186
+ bot.match(/omg/i) { second = true }
252
187
  bot.no_match(/omg/i)
253
188
 
254
- dsl.bot.event data
189
+ bot.event data
255
190
 
256
191
  expect(first).to eq false
257
192
  expect(second).to eq false
@@ -294,45 +229,70 @@ module Boty
294
229
  end
295
230
  end
296
231
 
297
- describe "#say", :users do
298
- let(:data) {{
299
- "type" => "message",
300
- "text" => "hey <@jabber>, omg me",
301
- "channel" => "omg"
302
- }}
303
-
232
+ describe "#say" do
304
233
  it "sends a string using the slack api to general (default) channel" do
305
234
  expect(Slack.chat).to receive(:post_message).
306
- with "something", channel: "general"
235
+ with "something", channel: "#general"
236
+
307
237
  bot.say "something"
308
238
  end
309
239
 
310
- it "send a message to the channel specified by a current binded message" do
240
+ it "sends a message to the channel specified by parameter" do
311
241
  expect(Slack.chat).to receive(:post_message).
312
- with "omg pugs!", channel: "omg"
242
+ with "omg pugs!", channel: "#omg"
313
243
 
314
- bot.respond(/omg me/) { say "omg pugs!" }
315
- bot.event data
244
+ bot.say "omg pugs!", channel: "#omg"
245
+ end
246
+
247
+ it "accepts extra parameters and send them to the slack api" do
248
+ expect(Slack.chat).to receive(:post_message).
249
+ with "omg pugs!", channel: "#omg", omg: "lol"
250
+
251
+ bot.say "omg pugs!", channel: "#omg", omg: "lol"
252
+ end
253
+
254
+ it "logs the response for the request sending the message" do
255
+ logger = Boty::Logger.adapter = Boty::Logger::Memory.new
256
+ allow(Slack.chat).to receive(:post_message).and_return "yay"
257
+ bot.say "omg pugs!", channel: "#omg", omg: "lol"
258
+
259
+ expect(logger.logs.last).to eq "Post response: yay."
316
260
  end
317
261
  end
318
262
 
319
- describe "#im", :session, :users do
263
+ describe "#im" do
320
264
  before do
321
- start_session
265
+ allow(Slack.users).to receive(:by_name).
266
+ with("julian").
267
+ and_return(Boty::Slack::User.new "id" => "U123", "name" => "julian")
322
268
  end
323
269
 
324
- let(:user_id) { "U4321" }
325
- let(:data) {{
326
- "type" => "message",
327
- "text" => "<@#{bot.name}>: omg",
328
- "user" => "U4321"
329
- }}
330
-
331
270
  it "sends a message in the back channel (particular, im...)" do
332
- bot.respond(/omg/) { im "lol" }
333
271
  expect(Boty::Slack.chat).to receive(:post_im).with "U4321", "lol"
334
272
 
335
- dsl.bot.event data
273
+ bot.im "lol", user_id: "U4321"
274
+ end
275
+
276
+ it "sends a message in the back channel to a particular user" do
277
+ expect(Slack.chat).to receive(:post_im).with "U123", "lol"
278
+
279
+ bot.im "lol", destiny: "julian"
280
+ end
281
+
282
+ it "logs that it will send an im", :logger do
283
+ allow(Slack.chat).to receive(:post_im)
284
+ bot.im "lol", destiny: "julian"
285
+
286
+ expect(logger.logs.last).to eq "Sending lol to julian."
287
+ end
288
+
289
+ it "logs when fails on discover the destiny", :logger do
290
+ allow(Slack.users).to receive(:by_name).
291
+ with("omg_doesnt_exists").
292
+ and_return nil
293
+ bot.im "lol", destiny: "omg_doesnt_exists"
294
+
295
+ expect(logger.logs.last).to eq "User not found, refusing to send im."
336
296
  end
337
297
  end
338
298
 
@@ -343,76 +303,47 @@ module Boty
343
303
  end
344
304
  end
345
305
 
346
- context "loading script/**/*.rb files", :fakefs, :users do
347
- let(:data) {{
348
- "type" => "message",
349
- "text" => "hey <@jabber>, pug me"
350
- }}
306
+ describe "#desc" do
307
+ it "describes the next command to be created" do
308
+ bot.desc "omg", "lol all the way down"
309
+ bot.respond(/omg/) {}
351
310
 
352
- before do
353
- FileUtils.mkdir "script" unless Dir.exists? "script"
354
- File.open "script/omg.rb", "w" do |f|
355
- f.write <<-RUBY
356
- respond(/pug me/i) do
357
- say "omg"
358
- end
359
- RUBY
360
- end
311
+ expect(bot.know_how).to include({
312
+ "omg" => "lol all the way down"
313
+ })
361
314
  end
362
315
 
363
- it "uses the logic in the script located at script/omg.rb" do
364
- expect(bot).to receive(:say).with "omg"
365
- bot.event data
316
+ it "describes the next match do be created" do
317
+ bot.desc "ula", "babula"
318
+ bot.match(/ula/) {}
319
+
320
+ expect(bot.know_how).to include({
321
+ "ula" => "babula"
322
+ })
366
323
  end
367
324
 
368
- context "describing commands", :fakefs, :users do
369
- before do
370
- File.open "script/omg.rb", "w" do |f|
371
- f.write <<-RUBY
372
- desc "pug me", "show some pretty random pug"
373
- respond(/pug me/i) do
374
- say "omg"
375
- end
376
-
377
- desc "just a description"
378
- respond(/just desc/i) do
379
- say "just description, raw command"
380
- end
381
-
382
- respond(/without desc/i) do
383
- say "no desc, raw command"
384
- end
385
-
386
- respond(/without desc sensitive/) do
387
- say "no desc, raw command"
388
- end
389
- RUBY
390
- end
391
- end
325
+ it "stores the description for a regex when no command usage is given" do
326
+ bot.desc "just a description"
327
+ bot.match(/just desc/i) {}
328
+ expect(bot.know_how).to include({
329
+ "/just desc/i" => "just a description"
330
+ })
331
+ end
392
332
 
393
- it "stores the description within command usage" do
394
- expect(bot.know_how).to include({
395
- "pug me" => "show some pretty random pug"
396
- })
397
- end
333
+ it "presents the commands without description as regexes" do
334
+ bot.match(/without desc/i) {}
398
335
 
399
- it "stores the description for a regex when no command usage is given" do
400
- expect(bot.know_how).to include({
401
- "/just desc/i" => "just a description"
402
- })
403
- end
336
+ expect(bot.know_how).to include({
337
+ "/without desc/i" => nil
338
+ })
339
+ end
404
340
 
405
- it "presents the commands without description as regexes" do
406
- expect(bot.know_how).to include({
407
- "/without desc/i" => nil
408
- })
409
- end
341
+ it "knows when a regex is case insensitive" do
342
+ bot.match(/without desc insensitive/)
410
343
 
411
- it "knows when a regex is case sensitive" do
412
- expect(bot.know_how).to include({
413
- "/without desc sensitive/" => nil
414
- })
415
- end
344
+ expect(bot.know_how).to include({
345
+ "/without desc insensitive/" => nil
346
+ })
416
347
  end
417
348
  end
418
349
  end
@@ -0,0 +1,125 @@
1
+ module Boty
2
+ RSpec.describe DSL do
3
+ let(:bot) { Bot.new({"id" => "U123", "name" => "jeeba"}) }
4
+ subject(:dsl) { described_class.new bot }
5
+
6
+ def message(text, extras = {})
7
+ bot.event({
8
+ "type" => "message",
9
+ "text" => text,
10
+ "user" => "U5678"
11
+ }.merge(extras))
12
+ end
13
+
14
+ before do
15
+ # used by message class
16
+ allow(Slack.users).to receive(:info).with("U5678").
17
+ and_return(Slack::User.new("id" => "U5678", "name" => "julian"))
18
+ end
19
+
20
+ methods_from_bot = [ :name, :brain, :know_how, :im, :say,
21
+ :desc, :respond, :match ]
22
+ methods_from_bot.each do |bot_method|
23
+ it "responds to :#{bot_method}" do
24
+ expect(dsl.respond_to?(bot_method)).to eq true
25
+ end
26
+ end
27
+
28
+ describe "#http" do
29
+ subject(:http) { dsl.http }
30
+
31
+ it { should be_a Boty::HTTP }
32
+ end
33
+
34
+ it "responds to a message" do
35
+ dsl.hear(/jeeba/i) do
36
+ say "Ohay <@#{user.id}>! I'm here, that's for sure."
37
+ end
38
+
39
+ expect(dsl).to receive(:say).
40
+ with "Ohay <@U5678>! I'm here, that's for sure."
41
+
42
+ message "jeeba, are you there?"
43
+ end
44
+
45
+ it "responds to a match" do
46
+ dsl.match(/with match/i) do
47
+ say "Ohay <@#{user.id}>! It matched."
48
+ end
49
+
50
+ expect(bot).to receive(:say).
51
+ with "Ohay <@U5678>! It matched."
52
+
53
+ message "called with match?"
54
+ end
55
+
56
+ it "does not respond to a message not binded" do
57
+ expect(bot).to_not receive(:say)
58
+
59
+ message "are you there?"
60
+ end
61
+
62
+ it "understands #hear as a message binding" do
63
+ dsl.hear(/anybody there\?/i) do
64
+ say "Me! Here."
65
+ end
66
+
67
+ expect(bot).to receive(:say).with("Me! Here.")
68
+
69
+ message "anybody there?"
70
+ end
71
+
72
+ it "uses the system logger", :logger do
73
+ dsl.logger.debug "x"
74
+
75
+ expect(dsl.logger.logs.last).to eq "x"
76
+ end
77
+
78
+ context "Binding strings" do
79
+ it "binds a string with #match" do
80
+ executed = false
81
+ dsl.match "omg" do executed = true end
82
+
83
+ expect { message "bbq omg lol." }.
84
+ to change { executed }.from(false).to true
85
+ end
86
+
87
+ it "binds a string with #respond" do
88
+ executed = false
89
+ dsl.respond "omg" do executed = true end
90
+
91
+ expect { message "<@jeeba>: bbq omg lol." }.
92
+ to change { executed }.from(false).to true
93
+ end
94
+ end
95
+
96
+ context "Direct message" do
97
+ before do
98
+ dsl.command(/do stuff/i) do
99
+ say "Doing some stuff"
100
+ end
101
+ end
102
+
103
+ it "responds when mentioned by name" do
104
+ expect(bot).to receive(:say).with "Doing some stuff"
105
+
106
+ message "<@jeeba>: do stuff!"
107
+ end
108
+
109
+ it "responds when mentioned by id" do
110
+ expect(bot).to receive(:say).with "Doing some stuff"
111
+ message "<@U1234>: do stuff!"
112
+ end
113
+
114
+ it "responds just once" do
115
+ expect(bot).to receive(:im).once
116
+ message "<@U1234>: knows"
117
+ end
118
+
119
+ it "responds when mentioned by both name and id in the same message" do
120
+ expect(bot).to receive(:im).once
121
+ message "<@U1234|jabber>: knows"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,5 @@
1
+ module Boty
2
+ RSpec.describe Boty::HTTP do
3
+ subject(:http) { Boty::HTTP.new }
4
+ end
5
+ end
@@ -15,6 +15,39 @@ module Boty
15
15
  end
16
16
  end
17
17
 
18
+ describe "#logger" do
19
+ it "returns the current adapter" do
20
+ memory_logger = Boty::Logger::Memory.new
21
+ logger.adapter = memory_logger
22
+ expect(client.logger).to eq memory_logger
23
+ end
24
+ end
25
+
26
+ describe Logger::Multi do
27
+ let(:adapter1) { double }
28
+ let(:adapter2) { double }
29
+
30
+ subject(:multi) { described_class.new [adapter1, adapter2] }
31
+
32
+ describe "#add" do
33
+ it "delegates the #add invocation to the underlying adapters" do
34
+ expect(adapter1).to receive(:add).with("omg", "lol")
35
+ expect(adapter2).to receive(:add).with("omg", "lol")
36
+
37
+ multi.add("omg", "lol")
38
+ end
39
+ end
40
+
41
+ describe "#level=" do
42
+ it "delegates the #level= invocation to the underlying adapters" do
43
+ expect(adapter1).to receive(:level=).with(::Logger::INFO)
44
+ expect(adapter2).to receive(:level=).with(::Logger::INFO)
45
+
46
+ multi.level = ::Logger::INFO
47
+ end
48
+ end
49
+ end
50
+
18
51
  describe Logger::Memory do
19
52
  subject(:adapter) { described_class.new }
20
53
 
@@ -6,7 +6,7 @@ module Boty
6
6
 
7
7
  context "bot[Boty::Bot] instance" do
8
8
  it "includes a `bot` instance in the example scope" do
9
- expect(bot).to be_a Boty::ScriptDSL
9
+ expect(bot).to be_a Boty::DSL
10
10
  end
11
11
  end
12
12
 
@@ -0,0 +1,27 @@
1
+ module Boty
2
+ RSpec.describe Boty::ScriptLoader, :fakefs, :users do
3
+ let(:dsl) { Boty::DSL.new Bot.new({ "name" => "jabber", "id" => "123" }) }
4
+ let(:data) {{
5
+ "type" => "message",
6
+ "text" => "hey <@jabber>, pug me"
7
+ }}
8
+
9
+ subject(:loader) { described_class.new Boty::DSL.new(bot) }
10
+
11
+ before do
12
+ FileUtils.mkdir "script" unless Dir.exists? "script"
13
+ File.open "script/omg.rb", "w" do |f|
14
+ f.write <<-RUBY
15
+ respond(/pug me/i) do
16
+ say "omg"
17
+ end
18
+ RUBY
19
+ end
20
+ end
21
+
22
+ it "uses the logic in the script located at script/omg.rb" do
23
+ expect(dsl).to receive(:say).with "omg"
24
+ dsl.bot.event data
25
+ end
26
+ end
27
+ end
@@ -44,25 +44,23 @@ module Boty
44
44
  "type": "omg",
45
45
  "text": "lol"
46
46
  }'
47
- session.start do |dsl|
48
- @dsl = dsl
47
+ dsl = nil
48
+ session.start do
49
+ dsl = self
49
50
  end
50
51
 
51
- expect(@dsl.bot).to receive(:event).with(JSON.parse data)
52
+ expect(dsl.bot).to receive(:event).with(JSON.parse data)
52
53
  faye.send_event :message, OpenStruct.new(data: data)
53
54
  end
54
55
 
55
56
  context "bot instantiation" do
56
- it "creates the Bot instance" do
57
- session.start do |bot|
58
- expect(bot).to be_a ScriptDSL
59
- end
60
- end
61
-
62
57
  it "intializes the Bot with the right data" do
63
- expect(Bot).to receive(:new).with({"id" => "1234"}, session)
58
+ bot_id = nil
59
+ session.start do
60
+ bot_id = bot.id
61
+ end
64
62
 
65
- session.start
63
+ expect(bot_id).to eq "1234"
66
64
  end
67
65
  end
68
66
  end
@@ -0,0 +1,34 @@
1
+ module Boty
2
+ module Slack
3
+ RSpec.describe Message, :users do
4
+ subject(:message) { described_class.new message_json }
5
+
6
+ let(:user_id) { "U7777" }
7
+ let(:user_name) { "julian" }
8
+
9
+ let(:message_json) {{
10
+ "type" => "message",
11
+ "text" => "omg lol bbq",
12
+ "user" => "U7777",
13
+ "channel" => "333",
14
+ "ts" => "1234",
15
+ "team" => "3452"
16
+ }}
17
+
18
+ it "uses Slack.users.info to create a User instance" do
19
+ expect(message.user.id).to eq "U7777"
20
+ expect(message.user.name).to eq "julian"
21
+ end
22
+
23
+ describe "#from?" do
24
+ it "returns true if the param is the message author" do
25
+ expect(message.from? "U7777").to eq true
26
+ end
27
+
28
+ it "returns true if the param `respond_to? :id` returning the message author" do
29
+ expect(message.from? OpenStruct.new(id: "U7777")).to eq true
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end