boty 0.0.17.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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