lita 2.7.2 → 3.0.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 (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