lita 3.3.1 → 4.0.0.rc1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +3 -0
  4. data/lib/lita.rb +45 -97
  5. data/lib/lita/adapter.rb +38 -17
  6. data/lib/lita/adapters/shell.rb +5 -3
  7. data/lib/lita/authorization.rb +109 -60
  8. data/lib/lita/builder.rb +38 -0
  9. data/lib/lita/callback.rb +37 -0
  10. data/lib/lita/cli.rb +2 -0
  11. data/lib/lita/config.rb +1 -18
  12. data/lib/lita/configurable.rb +29 -0
  13. data/lib/lita/configuration.rb +179 -0
  14. data/lib/lita/configuration_validator.rb +66 -0
  15. data/lib/lita/daemon.rb +4 -11
  16. data/lib/lita/default_configuration.rb +146 -0
  17. data/lib/lita/errors.rb +9 -0
  18. data/lib/lita/handler.rb +5 -264
  19. data/lib/lita/handler/chat_router.rb +130 -0
  20. data/lib/lita/handler/common.rb +114 -0
  21. data/lib/lita/handler/event_router.rb +77 -0
  22. data/lib/lita/handler/http_router.rb +26 -0
  23. data/lib/lita/handlers/authorization.rb +13 -18
  24. data/lib/lita/handlers/deprecation_check.rb +24 -0
  25. data/lib/lita/handlers/help.rb +5 -3
  26. data/lib/lita/handlers/info.rb +2 -2
  27. data/lib/lita/http_callback.rb +29 -0
  28. data/lib/lita/http_route.rb +41 -26
  29. data/lib/lita/namespace.rb +23 -0
  30. data/lib/lita/rack_app.rb +29 -2
  31. data/lib/lita/registry.rb +133 -0
  32. data/lib/lita/robot.rb +58 -20
  33. data/lib/lita/route_validator.rb +12 -4
  34. data/lib/lita/rspec.rb +23 -14
  35. data/lib/lita/rspec/handler.rb +93 -23
  36. data/lib/lita/rspec/matchers/chat_route_matcher.rb +48 -0
  37. data/lib/lita/rspec/matchers/deprecated.rb +36 -0
  38. data/lib/lita/rspec/matchers/event_route_matcher.rb +27 -0
  39. data/lib/lita/rspec/matchers/http_route_matcher.rb +18 -60
  40. data/lib/lita/user.rb +0 -6
  41. data/lib/lita/util.rb +1 -8
  42. data/lib/lita/version.rb +1 -1
  43. data/lita.gemspec +1 -0
  44. data/spec/lita/adapter_spec.rb +25 -7
  45. data/spec/lita/adapters/shell_spec.rb +24 -4
  46. data/spec/lita/authorization_spec.rb +57 -38
  47. data/spec/lita/builder_spec.rb +39 -0
  48. data/spec/lita/config_spec.rb +0 -24
  49. data/spec/lita/configuration_spec.rb +222 -0
  50. data/spec/lita/configuration_validator_spec.rb +112 -0
  51. data/spec/lita/daemon_spec.rb +2 -2
  52. data/spec/lita/default_configuration_spec.rb +254 -0
  53. data/spec/lita/handler/chat_router_spec.rb +192 -0
  54. data/spec/lita/handler/common_spec.rb +272 -0
  55. data/spec/lita/handler/event_router_spec.rb +54 -0
  56. data/spec/lita/handler/http_router_spec.rb +106 -0
  57. data/spec/lita/handler_spec.rb +20 -291
  58. data/spec/lita/handlers/authorization_spec.rb +9 -11
  59. data/spec/lita/handlers/deprecation_check_spec.rb +21 -0
  60. data/spec/lita/handlers/help_spec.rb +31 -9
  61. data/spec/lita/handlers/info_spec.rb +2 -2
  62. data/spec/lita/handlers/room_spec.rb +5 -3
  63. data/spec/lita/robot_spec.rb +30 -11
  64. data/spec/lita/rspec_spec.rb +71 -31
  65. data/spec/lita/user_spec.rb +2 -2
  66. data/spec/lita_spec.rb +62 -4
  67. data/spec/spec_helper.rb +7 -0
  68. data/templates/locales/en.yml +56 -4
  69. data/templates/plugin/gemspec.tt +1 -0
  70. data/templates/plugin/spec/spec_helper.tt +4 -0
  71. metadata +54 -8
  72. data/lib/lita/rspec/matchers/event_subscription_matcher.rb +0 -67
  73. data/lib/lita/rspec/matchers/route_matcher.rb +0 -69
  74. data/spec/lita/rack_app_spec.rb +0 -92
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Handler::EventRouter do
4
+ let(:robot) { instance_double("Lita::Robot", name: "Lita") }
5
+
6
+ subject do
7
+ Class.new do
8
+ extend Lita::Handler::EventRouter
9
+
10
+ def self.name
11
+ "Test"
12
+ end
13
+
14
+ on :connected, :greet
15
+
16
+ def greet(payload)
17
+ robot.send_message("Hi, #{payload[:name]}! Lita has started!")
18
+ end
19
+
20
+ on :block_test do |payload|
21
+ robot.send_message("#{payload[:data]} received via block!")
22
+ end
23
+
24
+ on :callable_test, lambda { |payload|
25
+ robot.send_message("#{payload[:data]} received via callable!")
26
+ }
27
+ end
28
+ end
29
+
30
+ describe ".trigger" do
31
+ it "invokes methods registered with .on and passes an arbitrary payload" do
32
+ expect(robot).to receive(:send_message).with(
33
+ "Hi, Carl! Lita has started!"
34
+ )
35
+ subject.trigger(robot, :connected, name: "Carl")
36
+ end
37
+
38
+ it "calls blocks that were passed to .on" do
39
+ expect(robot).to receive(:send_message).with("Data received via block!")
40
+ subject.trigger(robot, :block_test, data: "Data")
41
+ end
42
+
43
+ it "calls arbitrary callables that were passed to .on" do
44
+ expect(robot).to receive(:send_message).with("Data received via callable!")
45
+ subject.trigger(robot, :callable_test, data: "Data")
46
+ end
47
+
48
+ it "normalizes the event name" do
49
+ expect(robot).to receive(:send_message).twice
50
+ subject.trigger(robot, "connected")
51
+ subject.trigger(robot, " ConNected ")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ handler = Class.new do
4
+ extend Lita::Handler::HTTPRouter
5
+
6
+ namespace "test"
7
+
8
+ http.get "web", :web
9
+ http.post "path/with/:id", :variable
10
+ http.link "foo", :foo
11
+ http.get "heres/*a/glob/in/a/path", :glob
12
+ http.get ":var/otherwise/identical/path", :constraint, var: /\d+/
13
+ http.get ":var/otherwise/identical/path", :no_constraint
14
+ http.get("block") { |_request, response| response.write("block") }
15
+ http.get "middleware", :middleware
16
+
17
+ def web(_request, response)
18
+ response.write("it worked")
19
+ end
20
+
21
+ def variable(request, response)
22
+ id = request.env["router.params"][:id]
23
+ response.write("id is #{id}")
24
+ end
25
+
26
+ def glob(request, response)
27
+ segments = request.env["router.params"][:a]
28
+ response.write(segments.join("/"))
29
+ end
30
+
31
+ def constraint(_request, response)
32
+ response.write("constraint")
33
+ end
34
+
35
+ def no_constraint(_request, response)
36
+ response.write("no constraint")
37
+ end
38
+
39
+ def middleware(request, response)
40
+ response.write("middleware worked") if request.env["custom_rack_middleware_working"]
41
+ end
42
+ end
43
+
44
+ describe handler, lita_handler: true do
45
+ it "responds to requests for simple paths" do
46
+ response = http.get("/web")
47
+ expect(response.status).to eq(200)
48
+ expect(response.body).to eq("it worked")
49
+ end
50
+
51
+ it "responds to requests with variable paths" do
52
+ response = http.post("/path/with/some_id")
53
+ expect(response.status).to eq(200)
54
+ expect(response.body).to eq("id is some_id")
55
+ end
56
+
57
+ it "responds to requests with globs in their paths" do
58
+ response = http.get("heres/a/giant/glob/in/a/path")
59
+ expect(response.status).to eq(200)
60
+ expect(response.body).to eq("a/giant")
61
+ end
62
+
63
+ it "responds to requests with variable path constraints" do
64
+ response = http.get("/123/otherwise/identical/path")
65
+ expect(response.status).to eq(200)
66
+ expect(response.body).to eq("constraint")
67
+
68
+ response = http.get("/an/otherwise/identical/path")
69
+ expect(response.status).to eq(200)
70
+ expect(response.body).to eq("no constraint")
71
+ end
72
+
73
+ it "responds to HEAD requests for GET routes" do
74
+ response = http.head("/web")
75
+ expect(response.status).to eq(204)
76
+ expect(response.body).to be_empty
77
+ end
78
+
79
+ it "allows route callbacks to be provided as blocks" do
80
+ response = http.get("/block")
81
+ expect(response.status).to eq(200)
82
+ expect(response.body).to eq("block")
83
+ end
84
+ end
85
+
86
+ describe handler, lita_handler: true do
87
+ let(:middleware) do
88
+ Class.new do
89
+ def initialize(app)
90
+ @app = app
91
+ end
92
+
93
+ def call(env)
94
+ env["custom_rack_middleware_working"] = true
95
+ @app.call(env)
96
+ end
97
+ end
98
+ end
99
+
100
+ prepend_before { registry.config.http.middleware.push(middleware) }
101
+
102
+ it "uses any custom middlewares registered" do
103
+ response = http.get("/middleware")
104
+ expect(response.body).to eq("middleware worked")
105
+ end
106
+ end
@@ -1,311 +1,40 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Lita::Handler, lita: true do
4
- let(:robot) { instance_double("Lita::Robot", name: "Lita") }
5
- let(:user) { instance_double("Lita::User", name: "Test User") }
3
+ describe Lita::Handler, lita_handler: true do
4
+ before { registry.handlers.delete(described_class) }
6
5
 
7
- let(:message) do
8
- message = instance_double("Lita::Message", user: user, command?: false)
9
- allow(message).to receive(:match)
10
- message
11
- end
12
-
13
- let(:queue) { Queue.new }
14
-
15
- let(:guard_hook) do
16
- Class.new do
17
- def self.call(payload)
18
- if payload[:route].extensions[:guard]
19
- payload[:message].body.include?("code word")
20
- else
21
- true
22
- end
23
- end
24
- end
25
- end
26
-
27
- let(:response_hook) do
28
- Class.new do
29
- def self.call(payload)
30
- payload[:response].extensions[:data] = payload[:route].extensions[:data]
31
- end
32
- end
33
- end
34
-
35
- let(:handler_class) do
36
- Class.new(described_class) do
37
- route(/\w{3}/, :foo)
38
- route(/\w{4}/, :blah, command: true)
39
- route(/secret/, :secret, restrict_to: :admins)
40
- route(/danger/, :danger)
41
- route(/guard/, :guard, guard: true)
42
- route(/trigger route hook/, :trigger_route_hook, data: :foo)
43
-
44
- on :connected, :greet
45
- on :some_hook, :test_payload
46
-
47
- def self.default_config(config)
48
- config.foo = "bar"
49
- end
50
-
51
- def foo(_response)
52
- end
53
-
54
- def blah(_response)
55
- end
56
-
57
- def secret(_response)
58
- end
59
-
60
- def danger(_response)
61
- raise "The developer of this handler's got a bug in their code!"
62
- end
63
-
64
- def guard(_response)
65
- end
66
-
67
- def trigger_route_hook(_response)
68
- end
69
-
70
- def greet(payload)
71
- robot.send_message("Hi, #{payload[:name]}! Lita has started!")
72
- end
73
-
74
- def after_test(_response, queue)
75
- after(2) { queue.push("Waited 2 seconds!") }
76
- end
77
-
78
- def every_test(_response, queue)
79
- array = [1, 2, 3]
80
-
81
- every(2) do |timer|
82
- value = array.shift
83
-
84
- if value
85
- queue.push(value)
86
- else
87
- timer.stop
88
- end
89
- end
90
- end
91
-
92
- def infinite_every_test(response)
93
- thread = every(5) { "Looping forever!" }
94
- response.reply("Replying after timer!")
95
- thread
96
- end
97
-
98
- def self.name
99
- "Lita::Handlers::Test"
100
- end
101
- end
102
- end
103
-
104
- subject { described_class.new(robot) }
105
-
106
- describe ".dispatch" do
107
- it "routes a matching message to the supplied method" do
108
- allow(message).to receive(:body).and_return("bar")
109
- expect_any_instance_of(handler_class).to receive(:foo)
110
- handler_class.dispatch(robot, message)
111
- end
112
-
113
- it "routes a matching message even if addressed to the Robot" do
114
- allow(message).to receive(:body).and_return("#{robot.name}: bar")
115
- allow(message).to receive(:command?).and_return(true)
116
- expect_any_instance_of(handler_class).to receive(:foo)
117
- handler_class.dispatch(robot, message)
118
- end
119
-
120
- it "routes a command message to the supplied method" do
121
- allow(message).to receive(:body).and_return("#{robot.name}: bar")
122
- allow(message).to receive(:command?).and_return(true)
123
- expect_any_instance_of(handler_class).to receive(:blah)
124
- handler_class.dispatch(robot, message)
125
- end
126
-
127
- it "requires command routes to be addressed to the Robot" do
128
- allow(message).to receive(:body).and_return("blah")
129
- expect_any_instance_of(handler_class).not_to receive(:blah)
130
- handler_class.dispatch(robot, message)
131
- end
132
-
133
- it "doesn't route messages that don't match anything" do
134
- allow(message).to receive(:body).and_return("yo")
135
- expect_any_instance_of(handler_class).not_to receive(:foo)
136
- expect_any_instance_of(handler_class).not_to receive(:blah)
137
- handler_class.dispatch(robot, message)
138
- end
139
-
140
- it "dispatches to restricted routes if the user is in the auth group" do
141
- allow(message).to receive(:body).and_return("secret")
142
- allow(Lita::Authorization).to receive(:user_in_group?).and_return(true)
143
- expect_any_instance_of(handler_class).to receive(:secret)
144
- handler_class.dispatch(robot, message)
145
- end
146
-
147
- it "doesn't route unauthorized users' messages to restricted routes" do
148
- allow(message).to receive(:body).and_return("secret")
149
- allow(Lita::Authorization).to receive(:user_in_group?).and_return(false)
150
- expect_any_instance_of(handler_class).not_to receive(:secret)
151
- handler_class.dispatch(robot, message)
152
- end
153
-
154
- it "doesn't route messages from the bot back to the bot" do
155
- allow(message).to receive(:body).and_return("#{robot.name}: bar")
156
- allow(message).to receive(:command?).and_return(true)
157
- allow(message).to receive(:user).and_return(robot)
158
- expect_any_instance_of(handler_class).not_to receive(:blah)
159
- handler_class.dispatch(robot, message)
160
- end
161
-
162
- it "logs exceptions but doesn't crash the bot" do
163
- allow(message).to receive(:body).and_return("#{robot.name}: danger")
164
- allow(handler_class).to receive(:rspec_loaded?).and_return(false)
165
- expect(Lita.logger).to receive(:error).with(/Lita::Handlers::Test crashed/)
166
- expect { handler_class.dispatch(robot, message) }.not_to raise_error
167
- end
168
-
169
- it "re-raises exceptions when testing with RSpec" do
170
- allow(message).to receive(:body).and_return("#{robot.name}: danger")
171
- expect { handler_class.dispatch(robot, message) }.to raise_error
172
- end
173
-
174
- context "with a custom validate_route hook" do
175
- before { Lita.register_hook(:validate_route, guard_hook) }
176
- after { Lita.reset_hooks }
177
-
178
- it "matches if the hook returns true" do
179
- allow(message).to receive(:body).and_return("guard code word")
180
- expect_any_instance_of(handler_class).to receive(:guard)
181
- handler_class.dispatch(robot, message)
182
- end
183
-
184
- it "does not match if the hook returns false" do
185
- allow(message).to receive(:body).and_return("guard")
186
- expect_any_instance_of(handler_class).not_to receive(:guard)
187
- handler_class.dispatch(robot, message)
6
+ it "includes chat routes" do
7
+ registry.register_handler(:foo) do
8
+ route(/foo/) do |response|
9
+ response.reply("bar")
188
10
  end
189
11
  end
190
12
 
191
- context "with a custom trigger_route hook" do
192
- before { Lita.register_hook(:trigger_route, response_hook) }
193
- after { Lita.reset_hooks }
13
+ send_message("foo")
194
14
 
195
- it "adds data to the response's extensions" do
196
- allow(message).to receive(:body).and_return("trigger route hook")
197
- allow_any_instance_of(handler_class).to receive(:trigger_route_hook) do |_robot, response|
198
- expect(response.extensions[:data]).to eq(:foo)
199
- end
200
- handler_class.dispatch(robot, message)
201
- end
202
- end
15
+ expect(replies.last).to include("bar")
203
16
  end
204
17
 
205
- describe ".namespace" do
206
- it "provides a snake cased namespace for the handler" do
207
- handler_class = Class.new(described_class) do
208
- def self.name
209
- "Lita::Handlers::FooBarBaz"
210
- end
18
+ it "includes HTTP routes" do
19
+ registry.register_handler(:foo) do
20
+ http.get "foo" do |_request, response|
21
+ response.write("bar")
211
22
  end
212
- expect(handler_class.namespace).to eq("foo_bar_baz")
213
- end
214
-
215
- it "raises an exception if the handler doesn't define self.name" do
216
- handler_class = Class.new(described_class)
217
- expect { handler_class.namespace }.to raise_error
218
23
  end
219
- end
220
24
 
221
- describe ".trigger" do
222
- it "invokes methods registered with .on and passes an arbitrary payload" do
223
- expect(robot).to receive(:send_message).with(
224
- "Hi, Carl! Lita has started!"
225
- )
226
- handler_class.trigger(robot, :connected, name: "Carl")
227
- end
25
+ http_client = Faraday::Connection.new { |c| c.adapter(:rack, Lita::RackApp.new(robot)) }
26
+ response = http_client.get("/foo")
228
27
 
229
- it "normalizes the event name" do
230
- expect(robot).to receive(:send_message).twice
231
- handler_class.trigger(robot, "connected")
232
- handler_class.trigger(robot, " ConNected ")
233
- end
28
+ expect(response.body).to eq("bar")
234
29
  end
235
30
 
236
- describe "#config" do
237
- before { Lita.register_handler(handler_class) }
238
- subject { handler_class.new(robot) }
239
-
240
- it "returns a Lita config" do
241
- expect(subject.config).to be_a(Lita::Config)
31
+ it "includes event routes" do
32
+ registry.register_handler(:foo) do
33
+ on(:some_event) { robot.send_message("payload received") }
242
34
  end
243
35
 
244
- it "contains the handler's config settings" do
245
- expect(subject.config.foo).to eq("bar")
246
- end
247
- end
248
-
249
- describe "#http" do
250
- it "returns a Faraday connection" do
251
- expect(subject.http).to be_a(Faraday::Connection)
252
- end
253
-
254
- it "sets a default user agent" do
255
- expect(subject.http.headers["User-Agent"]).to eq("Lita v#{Lita::VERSION}")
256
- end
257
-
258
- it "merges in user-supplied options" do
259
- connection = subject.http(headers: {
260
- "User-Agent" => "Foo", "X-Bar" => "Baz"
261
- })
262
- expect(connection.headers["User-Agent"]).to eq("Foo")
263
- expect(connection.headers["X-Bar"]).to eq("Baz")
264
- end
265
-
266
- it "passes blocks on to Faraday" do
267
- connection = subject.http { |builder| builder.response :logger }
268
- expect(connection.builder.handlers).to include(Faraday::Response::Logger)
269
- end
270
- end
271
-
272
- describe "#log" do
273
- it "returns the Lita logger" do
274
- expect(subject.log).to eq(Lita.logger)
275
- end
276
- end
277
-
278
- describe "timer methods" do
279
- let(:response) { instance_double("Lita::Response") }
280
-
281
- subject { handler_class.new(robot) }
282
-
283
- before { allow_any_instance_of(Lita::Timer).to receive(:sleep) }
284
-
285
- describe "#after" do
286
- it "triggers the block after the given number of seconds" do
287
- subject.after_test(response, queue)
288
- expect(queue.pop).to eq("Waited 2 seconds!")
289
- expect { queue.pop(true) }.to raise_error(ThreadError)
290
- end
291
- end
292
-
293
- describe "#every" do
294
- it "triggers the block until the timer is stopped" do
295
- subject.every_test(response, queue)
296
- expect(queue.pop).to eq(1)
297
- expect(queue.pop).to eq(2)
298
- expect(queue.pop).to eq(3)
299
- expect { queue.pop(true) }.to raise_error(ThreadError)
300
- end
301
- end
36
+ expect(robot).to receive(:send_message).with("payload received")
302
37
 
303
- context "with an infinite timer" do
304
- it "doesn't block the handler's thread" do
305
- expect(response).to receive(:reply)
306
- thread = subject.infinite_every_test(response)
307
- thread.kill
308
- end
309
- end
38
+ robot.trigger(:some_event)
310
39
  end
311
40
  end