lita 2.7.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +26 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +6 -470
  6. data/Rakefile +3 -3
  7. data/lib/lita.rb +27 -19
  8. data/lib/lita/adapter.rb +46 -5
  9. data/lib/lita/adapters/shell.rb +18 -13
  10. data/lib/lita/authorization.rb +1 -1
  11. data/lib/lita/cli.rb +37 -23
  12. data/lib/lita/common.rb +35 -0
  13. data/lib/lita/config.rb +33 -13
  14. data/lib/lita/daemon.rb +15 -12
  15. data/lib/lita/handler.rb +49 -9
  16. data/lib/lita/handlers/authorization.rb +47 -47
  17. data/lib/lita/handlers/help.rb +16 -17
  18. data/lib/lita/handlers/info.rb +38 -0
  19. data/lib/lita/handlers/room.rb +32 -0
  20. data/lib/lita/http_route.rb +30 -19
  21. data/lib/lita/message.rb +3 -6
  22. data/lib/lita/rack_app.rb +11 -89
  23. data/lib/lita/response.rb +5 -15
  24. data/lib/lita/robot.rb +26 -10
  25. data/lib/lita/rspec.rb +6 -8
  26. data/lib/lita/rspec/handler.rb +49 -121
  27. data/lib/lita/rspec/matchers/event_subscription_matcher.rb +67 -0
  28. data/lib/lita/rspec/matchers/http_route_matcher.rb +72 -0
  29. data/lib/lita/rspec/matchers/route_matcher.rb +69 -0
  30. data/lib/lita/source.rb +5 -18
  31. data/lib/lita/timer.rb +45 -0
  32. data/lib/lita/user.rb +51 -4
  33. data/lib/lita/util.rb +5 -5
  34. data/lib/lita/version.rb +1 -1
  35. data/lita.gemspec +6 -3
  36. data/spec/lita/adapter_spec.rb +10 -2
  37. data/spec/lita/adapters/shell_spec.rb +3 -3
  38. data/spec/lita/authorization_spec.rb +11 -11
  39. data/spec/lita/config_spec.rb +8 -0
  40. data/spec/lita/daemon_spec.rb +65 -0
  41. data/spec/lita/handler_spec.rb +50 -11
  42. data/spec/lita/handlers/authorization_spec.rb +1 -1
  43. data/spec/lita/handlers/info_spec.rb +31 -0
  44. data/spec/lita/handlers/room_spec.rb +20 -0
  45. data/spec/lita/logger_spec.rb +1 -1
  46. data/spec/lita/message_spec.rb +4 -4
  47. data/spec/lita/rack_app_spec.rb +92 -0
  48. data/spec/lita/response_spec.rb +17 -8
  49. data/spec/lita/robot_spec.rb +23 -14
  50. data/spec/lita/rspec_spec.rb +1 -1
  51. data/spec/lita/source_spec.rb +0 -16
  52. data/spec/lita/timer_spec.rb +30 -0
  53. data/spec/lita/user_spec.rb +66 -6
  54. data/spec/lita_spec.rb +37 -0
  55. data/spec/spec_helper.rb +11 -0
  56. data/templates/locales/en.yml +90 -0
  57. data/templates/plugin/Rakefile +1 -1
  58. data/templates/plugin/lib/lita/plugin_type/plugin.tt +4 -0
  59. data/templates/plugin/locales/en.yml.tt +4 -0
  60. metadata +77 -18
  61. data/lib/lita/handlers/web.rb +0 -25
  62. data/spec/lita/handlers/web_spec.rb +0 -19
@@ -1,6 +1,6 @@
1
1
  describe Lita::Adapters::Shell do
2
2
  let(:robot) do
3
- double("Lita::Robot", name: "Lita", mention_name: "LitaBot", alias: "/")
3
+ instance_double("Lita::Robot", name: "Lita", mention_name: "LitaBot", alias: "/")
4
4
  end
5
5
 
6
6
  subject { described_class.new(robot) }
@@ -37,12 +37,12 @@ describe Lita::Adapters::Shell do
37
37
  expect(subject).to receive(:puts) do |messages|
38
38
  expect(messages.first).to include("bar")
39
39
  end
40
- subject.send_messages(double("target"), "bar")
40
+ subject.send_messages(instance_double("Lita::Source"), "bar")
41
41
  end
42
42
 
43
43
  it "doesn't output empty messages" do
44
44
  expect(subject).to receive(:puts).with([])
45
- subject.send_messages(double("target"), "")
45
+ subject.send_messages(instance_double("Lita::Source"), "")
46
46
  end
47
47
  end
48
48
 
@@ -1,8 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Lita::Authorization, lita: true do
4
- let(:requesting_user) { double("Lita::User", id: "1") }
5
- let(:user) { double("Lita::User", id: "2") }
4
+ let(:requesting_user) { instance_double("Lita::User", id: "1") }
5
+ let(:user) { instance_double("Lita::User", id: "2") }
6
6
 
7
7
  before do
8
8
  Lita.config.robot.admins = ["1"]
@@ -11,7 +11,7 @@ describe Lita::Authorization, lita: true do
11
11
  describe ".add_user_to_group" do
12
12
  it "adds users to an auth group" do
13
13
  described_class.add_user_to_group(requesting_user, user, "employees")
14
- expect(described_class.user_in_group?(user, "employees")).to be_true
14
+ expect(described_class.user_in_group?(user, "employees")).to be true
15
15
  end
16
16
 
17
17
  it "can only be called by admins" do
@@ -22,12 +22,12 @@ describe Lita::Authorization, lita: true do
22
22
  "employees"
23
23
  )
24
24
  expect(result).to eq(:unauthorized)
25
- expect(described_class.user_in_group?(user, "employees")).to be_false
25
+ expect(described_class.user_in_group?(user, "employees")).to be false
26
26
  end
27
27
 
28
28
  it "normalizes the group name" do
29
29
  described_class.add_user_to_group(requesting_user, user, "eMPLoYeeS")
30
- expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be_true
30
+ expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be true
31
31
  end
32
32
  end
33
33
 
@@ -35,7 +35,7 @@ describe Lita::Authorization, lita: true do
35
35
  it "removes users from an auth group" do
36
36
  described_class.add_user_to_group(requesting_user, user, "employees")
37
37
  described_class.remove_user_from_group(requesting_user, user, "employees")
38
- expect(described_class.user_in_group?(user, "employees")).to be_false
38
+ expect(described_class.user_in_group?(user, "employees")).to be false
39
39
  end
40
40
 
41
41
  it "can only be called by admins" do
@@ -47,19 +47,19 @@ describe Lita::Authorization, lita: true do
47
47
  "employees"
48
48
  )
49
49
  expect(result).to eq(:unauthorized)
50
- expect(described_class.user_in_group?(user, "employees")).to be_true
50
+ expect(described_class.user_in_group?(user, "employees")).to be true
51
51
  end
52
52
 
53
53
  it "normalizes the group name" do
54
54
  described_class.add_user_to_group(requesting_user, user, "eMPLoYeeS")
55
55
  described_class.remove_user_from_group(requesting_user, user, "EmployeeS")
56
- expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be_false
56
+ expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be false
57
57
  end
58
58
  end
59
59
 
60
60
  describe ".user_in_group?" do
61
61
  it "returns false if the user is in the group" do
62
- expect(described_class.user_in_group?(user, "employees")).to be_false
62
+ expect(described_class.user_in_group?(user, "employees")).to be false
63
63
  end
64
64
 
65
65
  it "delegates to .user_is_admin? if the group is admins" do
@@ -70,12 +70,12 @@ describe Lita::Authorization, lita: true do
70
70
 
71
71
  describe ".user_is_admin?" do
72
72
  it "returns true if the user's ID is in the config" do
73
- expect(described_class.user_is_admin?(requesting_user)).to be_true
73
+ expect(described_class.user_is_admin?(requesting_user)).to be true
74
74
  end
75
75
 
76
76
  it "returns false if the user's ID is not in the config" do
77
77
  Lita.config.robot.admins = nil
78
- expect(described_class.user_is_admin?(user)).to be_false
78
+ expect(described_class.user_is_admin?(user)).to be false
79
79
  end
80
80
  end
81
81
 
@@ -48,6 +48,7 @@ describe Lita::Config do
48
48
  end
49
49
 
50
50
  it "doesn't attempt to load lita_config.rb if it doesn't exist" do
51
+ allow(File).to receive(:exist?).and_return(false)
51
52
  expect(described_class).not_to receive(:load)
52
53
  described_class.load_user_config
53
54
  end
@@ -59,4 +60,11 @@ describe Lita::Config do
59
60
  expect { described_class.load_user_config }.to raise_error(SystemExit)
60
61
  end
61
62
  end
63
+
64
+ describe "#finalize" do
65
+ it "freezes the configuration" do
66
+ subject.finalize
67
+ expect { subject.robot = "Assignment is impossible!" }.to raise_error(RuntimeError, /frozen/)
68
+ end
69
+ end
62
70
  end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Daemon do
4
+ let(:stdout) { StringIO.new }
5
+ let(:stderr) { StringIO.new }
6
+ let(:log_file) { StringIO.new }
7
+
8
+ before do
9
+ allow(Process).to receive(:daemon)
10
+ allow(Process).to receive(:kill)
11
+ allow(File).to receive(:new).and_return("log")
12
+ allow(File).to receive(:open)
13
+ allow(File).to receive(:read)
14
+ allow_any_instance_of(described_class).to receive(:stdout).and_return(stdout)
15
+ allow_any_instance_of(described_class).to receive(:stderr).and_return(stderr)
16
+ end
17
+
18
+ subject { described_class.new("/tmp/lita_pid", "/tmp/lita_log", false) }
19
+
20
+ describe "#daemonize" do
21
+ it "daemonizes the running process" do
22
+ expect(Process).to receive(:daemon).with(true)
23
+ subject.daemonize
24
+ end
25
+
26
+ context "when the user has not requested that existing processes should be killed" do
27
+ it "aborts if a Lita process is already running" do
28
+ allow(File).to receive(:exists?).and_return(true)
29
+ expect(subject).to receive(:abort)
30
+ subject.daemonize
31
+ end
32
+ end
33
+
34
+ context "when the user has requested that existing process be killed" do
35
+ subject { described_class.new("/tmp/lita_pid", "/tmp/lita_log", true) }
36
+
37
+ it "kills existing processes" do
38
+ allow(File).to receive(:exists?).and_return(true)
39
+ expect(Process).to receive(:kill)
40
+ subject.daemonize
41
+ end
42
+
43
+ it "aborts if it can't kill an existing process" do
44
+ allow(File).to receive(:exists?).and_return(true)
45
+ allow(Process).to receive(:kill).and_raise(Errno::ESRCH)
46
+ expect(subject).to receive(:abort)
47
+ subject.daemonize
48
+ end
49
+ end
50
+
51
+ it "redirects stdout to the log file" do
52
+ allow(File).to receive(:new).with("/tmp/lita_log", "a").and_return(log_file)
53
+ subject.daemonize
54
+ stdout.write "foo"
55
+ expect(log_file.string).to eq("foo")
56
+ end
57
+
58
+ it "redirects stderr to the log file" do
59
+ allow(File).to receive(:new).with("/tmp/lita_log", "a").and_return(log_file)
60
+ subject.daemonize
61
+ stderr.write "bar"
62
+ expect(log_file.string).to eq("bar")
63
+ end
64
+ end
65
+ end
@@ -1,15 +1,17 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Lita::Handler, lita: true do
4
- let(:robot) { double("Lita::Robot", name: "Lita") }
5
- let(:user) { double("Lita::User", name: "Test User") }
4
+ let(:robot) { instance_double("Lita::Robot", name: "Lita") }
5
+ let(:user) { instance_double("Lita::User", name: "Test User") }
6
6
 
7
7
  let(:message) do
8
- message = double("Lita::Message", user: user, command?: false)
8
+ message = instance_double("Lita::Message", user: user, command?: false)
9
9
  allow(message).to receive(:match)
10
10
  message
11
11
  end
12
12
 
13
+ let(:queue) { Queue.new }
14
+
13
15
  let(:handler_class) do
14
16
  Class.new(described_class) do
15
17
  route(/\w{3}/, :foo)
@@ -17,8 +19,6 @@ describe Lita::Handler, lita: true do
17
19
  route(/secret/, :secret, restrict_to: :admins)
18
20
  route(/danger/, :danger)
19
21
 
20
- http.get "web", :web
21
-
22
22
  on :connected, :greet
23
23
  on :some_hook, :test_payload
24
24
 
@@ -31,9 +31,6 @@ describe Lita::Handler, lita: true do
31
31
  def secret(response)
32
32
  end
33
33
 
34
- def web(request, response)
35
- end
36
-
37
34
  def danger(response)
38
35
  raise "The developer of this handler's got a bug in their code!"
39
36
  end
@@ -42,6 +39,24 @@ describe Lita::Handler, lita: true do
42
39
  robot.send_message("Hi, #{payload[:name]}! Lita has started!")
43
40
  end
44
41
 
42
+ def after_test(response, queue)
43
+ after(2) { |timer| queue.push("Waited 2 seconds!") }
44
+ end
45
+
46
+ def every_test(response, queue)
47
+ array = [1, 2, 3]
48
+
49
+ every(2) do |timer|
50
+ value = array.shift
51
+
52
+ if value
53
+ queue.push(value)
54
+ else
55
+ timer.stop
56
+ end
57
+ end
58
+ end
59
+
45
60
  def self.name
46
61
  "Lita::Handlers::Test"
47
62
  end
@@ -109,9 +124,7 @@ describe Lita::Handler, lita: true do
109
124
  it "logs exceptions but doesn't crash the bot" do
110
125
  allow(message).to receive(:body).and_return("#{robot.name}: danger")
111
126
  allow(handler_class).to receive(:rspec_loaded?).and_return(false)
112
- expect(Lita.logger).to receive(:error).with(
113
- %r{Lita::Handlers::Test crashed}
114
- )
127
+ expect(Lita.logger).to receive(:error).with(/Lita::Handlers::Test crashed/)
115
128
  expect { handler_class.dispatch(robot, message) }.not_to raise_error
116
129
  end
117
130
 
@@ -174,4 +187,30 @@ describe Lita::Handler, lita: true do
174
187
  expect(connection.builder.handlers).to include(Faraday::Response::Logger)
175
188
  end
176
189
  end
190
+
191
+ describe "timer methods" do
192
+ let(:response) { instance_double("Lita::Response") }
193
+
194
+ subject { handler_class.new(robot) }
195
+
196
+ before { allow_any_instance_of(Lita::Timer).to receive(:sleep) }
197
+
198
+ describe "#after" do
199
+ it "triggers the block after the given number of seconds" do
200
+ subject.after_test(response, queue)
201
+ expect(queue.pop).to eq("Waited 2 seconds!")
202
+ expect { queue.pop(true) }.to raise_error(ThreadError)
203
+ end
204
+ end
205
+
206
+ describe "#every" do
207
+ it "triggers the block until the timer is stopped" do
208
+ subject.every_test(response, queue)
209
+ expect(queue.pop).to eq(1)
210
+ expect(queue.pop).to eq(2)
211
+ expect(queue.pop).to eq(3)
212
+ expect { queue.pop(true) }.to raise_error(ThreadError)
213
+ end
214
+ end
215
+ end
177
216
  end
@@ -7,7 +7,7 @@ describe Lita::Handlers::Authorization, lita_handler: true do
7
7
  ).and_return(true)
8
8
  end
9
9
 
10
- let(:target_user) { double("Lita::User", id: "1", name: "Carl") }
10
+ let(:target_user) { instance_double("Lita::User", id: "1", name: "Carl") }
11
11
 
12
12
  it { routes_command("auth add foo bar").to(:add) }
13
13
  it { routes_command("auth add foo@bar.com baz").to(:add) }
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Handlers::Info, lita_handler: true do
4
+ it { routes_command("info").to(:chat) }
5
+ it { routes_http(:get, "/lita/info").to(:web) }
6
+
7
+ let(:request) { double("Rack::Request") }
8
+ let(:response) { Rack::Response.new }
9
+
10
+ describe "#chat" do
11
+ it "responds with the current version of Lita" do
12
+ send_command("info")
13
+ expect(replies.last).to include(Lita::VERSION)
14
+ end
15
+
16
+ it "responds with a link to the website" do
17
+ send_command("info")
18
+ expect(replies.last).to include("lita.io")
19
+ end
20
+ end
21
+
22
+ describe "#web" do
23
+ it "returns JSON with info about the running robot" do
24
+ subject.web(request, response)
25
+ expect(response.headers["Content-Type"]).to eq("application/json")
26
+ expect(response.body.join).to include(
27
+ %{"lita_version":"#{Lita::VERSION}"}
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ describe Lita::Handlers::Room, lita_handler: true do
2
+ it { routes_command("join #lita.io").to(:join) }
3
+ it { routes_command("part #lita.io").to(:part) }
4
+
5
+ before { allow(Lita::Authorization).to receive(:user_is_admin?).with(user).and_return(true) }
6
+
7
+ describe "#join" do
8
+ it "calls Robot#join with the provided ID" do
9
+ expect(robot).to receive(:join).with("#lita.io")
10
+ send_command("join #lita.io")
11
+ end
12
+ end
13
+
14
+ describe "#part" do
15
+ it "calls Robot#part with the provided ID" do
16
+ expect(robot).to receive(:part).with("#lita.io")
17
+ send_command("part #lita.io")
18
+ end
19
+ end
20
+ end
@@ -21,6 +21,6 @@ describe Lita::Logger do
21
21
  stub_const("STDERR", stderr)
22
22
  logger = described_class.get_logger(:debug)
23
23
  logger.fatal "foo"
24
- expect(stderr.string).to match(%r{^\[.+\] FATAL: foo$})
24
+ expect(stderr.string).to match(/^\[.+\] FATAL: foo$/)
25
25
  end
26
26
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Lita::Message do
4
4
  let(:robot) do
5
- double("Lita::Robot", name: "Lita", mention_name: "LitaBot", alias: ".")
5
+ instance_double("Lita::Robot", name: "Lita", mention_name: "LitaBot", alias: ".")
6
6
  end
7
7
 
8
8
  subject do
@@ -20,12 +20,12 @@ describe Lita::Message do
20
20
  describe "#args" do
21
21
  it "returns an array of the 2nd through nth word in the message" do
22
22
  subject = described_class.new(robot, "args foo bar", "Carl")
23
- expect(subject.args).to eq(["foo", "bar"])
23
+ expect(subject.args).to eq(%w(foo bar))
24
24
  end
25
25
 
26
26
  it "escapes messages that have mismatched quotes" do
27
27
  subject = described_class.new(robot, "args it's working", "Carl")
28
- expect(subject.args).to eq(["it's", "working"])
28
+ expect(subject.args).to eq(%w(it's working))
29
29
  end
30
30
  end
31
31
 
@@ -92,7 +92,7 @@ describe Lita::Message do
92
92
  )
93
93
  expect(robot).to receive(:send_messages) do |source, *strings|
94
94
  expect(source).to be_a_private_message
95
- expect(strings).to eq(["foo", "bar"])
95
+ expect(strings).to eq(%w(foo bar))
96
96
  end
97
97
  subject.reply_privately("foo", "bar")
98
98
  end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::RackApp do
4
+ let(:handler_class) do
5
+ Class.new(Lita::Handler) do
6
+ http.get "web", :web
7
+ http.post "path/with/:id", :variable
8
+ http.link "foo", :foo
9
+ http.get "heres/*a/glob/in/a/path", :glob
10
+ http.get ":var/otherwise/identical/path", :constraint, var: /\d+/
11
+ http.get ":var/otherwise/identical/path", :no_constraint
12
+
13
+ def web(request, response)
14
+ response.write("it worked")
15
+ end
16
+
17
+ def variable(request, response)
18
+ id = request.env["router.params"][:id]
19
+ response.write("id is #{id}")
20
+ end
21
+
22
+ def glob(request, response)
23
+ segments = request.env["router.params"][:a]
24
+ response.write(segments.join("/"))
25
+ end
26
+
27
+ def constraint(request, response)
28
+ response.write("constraint")
29
+ end
30
+
31
+ def no_constraint(request, response)
32
+ response.write("no constraint")
33
+ end
34
+
35
+ def self.name
36
+ "Lita::Handlers::Test"
37
+ end
38
+ end
39
+ end
40
+
41
+ let(:robot) { instance_double("Lita::Robot") }
42
+
43
+ before { allow(Lita).to receive(:handlers).and_return([handler_class]) }
44
+
45
+ subject { described_class.new(robot) }
46
+
47
+ it "responds to requests for simple paths" do
48
+ env = Rack::MockRequest.env_for("/web")
49
+ status, _headers, body_proxy = subject.call(env)
50
+ expect(status).to eq(200)
51
+ expect(body_proxy.body.first).to eq("it worked")
52
+ end
53
+
54
+ it "responds to requests with variable paths" do
55
+ env = Rack::MockRequest.env_for("/path/with/some_id", method: "POST")
56
+ status, _headers, body_proxy = subject.call(env)
57
+ expect(status).to eq(200)
58
+ expect(body_proxy.body.first).to eq("id is some_id")
59
+ end
60
+
61
+ it "responds to requests with globs in their paths" do
62
+ env = Rack::MockRequest.env_for("/heres/a/giant/glob/in/a/path")
63
+ status, _headers, body_proxy = subject.call(env)
64
+ expect(status).to eq(200)
65
+ expect(body_proxy.body.first).to eq("a/giant")
66
+ end
67
+
68
+ it "responds to requests with variable path constraints" do
69
+ env = Rack::MockRequest.env_for("/123/otherwise/identical/path")
70
+ status, _headers, body_proxy = subject.call(env)
71
+ expect(status).to eq(200)
72
+ expect(body_proxy.body.first).to eq("constraint")
73
+
74
+ env = Rack::MockRequest.env_for("/an/otherwise/identical/path")
75
+ status, _headers, body_proxy = subject.call(env)
76
+ expect(status).to eq(200)
77
+ expect(body_proxy.body.first).to eq("no constraint")
78
+ end
79
+
80
+ it "responds to HEAD requests for GET routes" do
81
+ env = Rack::MockRequest.env_for("/web", method: "HEAD")
82
+ status, _headers, body = subject.call(env)
83
+ expect(status).to eq(204)
84
+ expect(body).to be_empty
85
+ end
86
+
87
+ it "doesn't respond to HEAD requests for non-GET routes" do
88
+ env = Rack::MockRequest.env_for("/path/with/some_id", method: "HEAD")
89
+ status, _headers, _body = subject.call(env)
90
+ expect(status).to eq(405)
91
+ end
92
+ end