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,10 +1,9 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Lita::Response do
4
- subject { described_class.new(message, pattern) }
4
+ subject { described_class.new(message, /dummy regexp/) }
5
5
 
6
- let(:message) { double("Lita::Message").as_null_object }
7
- let(:pattern) { double("Regexp").as_null_object }
6
+ let(:message) { instance_double("Lita::Message").as_null_object }
8
7
 
9
8
  [:args, :reply, :user, :command?].each do |method|
10
9
  it "delegates :#{method} to #message" do
@@ -13,10 +12,20 @@ describe Lita::Response do
13
12
  end
14
13
  end
15
14
 
16
- it "supports the deprecated Response.new(message, matches: matches) API" do
17
- matches = ["foo"]
18
- expect(Lita.logger).to receive(:warn)
19
- subject = described_class.new(message, matches: matches)
20
- expect(subject.matches).to eq(matches)
15
+ describe "#matches" do
16
+ it "matches the pattern against the message" do
17
+ expect(message).to receive(:match).with(subject.pattern)
18
+ subject.matches
19
+ end
20
+ end
21
+
22
+ describe "#match_data" do
23
+ let(:body) { instance_double("String") }
24
+
25
+ it "matches the message body against the pattern" do
26
+ allow(message).to receive(:body).and_return(body)
27
+ expect(subject.pattern).to receive(:match).with(message.body)
28
+ subject.match_data
29
+ end
21
30
  end
22
31
  end
@@ -15,8 +15,8 @@ describe Lita::Robot do
15
15
  end
16
16
 
17
17
  context "with registered handlers" do
18
- let(:handler1) { double("Handler 1").as_null_object }
19
- let(:handler2) { double("Handler 2").as_null_object }
18
+ let(:handler1) { class_double("Lita::Handler", http_routes: [], trigger: nil) }
19
+ let(:handler2) { class_double("Lita::Handler", http_routes: [], trigger: nil) }
20
20
 
21
21
  before do
22
22
  allow(Lita).to receive(:handlers).and_return([handler1, handler2])
@@ -40,11 +40,12 @@ describe Lita::Robot do
40
40
  end
41
41
 
42
42
  describe "#run" do
43
- let(:thread) { double("Thread", :abort_on_exception= => true, join: nil) }
43
+ let(:thread) { instance_double("Thread", :abort_on_exception= => true, join: nil) }
44
44
 
45
45
  before do
46
46
  allow_any_instance_of(Lita::Adapters::Shell).to receive(:run)
47
- allow_any_instance_of(Thin::Server).to receive(:start)
47
+ allow_any_instance_of(Puma::Server).to receive(:run)
48
+ allow_any_instance_of(Puma::Server).to receive(:add_tcp_listener)
48
49
 
49
50
  allow(Thread).to receive(:new) do |&block|
50
51
  block.call
@@ -58,13 +59,7 @@ describe Lita::Robot do
58
59
  end
59
60
 
60
61
  it "starts the web server" do
61
- expect_any_instance_of(Thin::Server).to receive(:start)
62
- subject.run
63
- end
64
-
65
- it "doesn't silence thin if config.http.debug is true" do
66
- Lita.config.http.debug = true
67
- expect_any_instance_of(Thin::Server).not_to receive(:silent=)
62
+ expect_any_instance_of(Puma::Server).to receive(:run)
68
63
  subject.run
69
64
  end
70
65
 
@@ -77,21 +72,35 @@ describe Lita::Robot do
77
72
  end
78
73
  end
79
74
 
75
+ describe "#join" do
76
+ it "delegates to the adapter" do
77
+ expect_any_instance_of(Lita::Adapters::Shell).to receive(:join).with("#lita.io")
78
+ subject.join("#lita.io")
79
+ end
80
+ end
81
+
82
+ describe "#part" do
83
+ it "delegates to the adapter" do
84
+ expect_any_instance_of(Lita::Adapters::Shell).to receive(:part).with("#lita.io")
85
+ subject.part("#lita.io")
86
+ end
87
+ end
88
+
80
89
  describe "#send_message" do
81
- let(:source) { double("Source") }
90
+ let(:source) { instance_double("Lita::Source") }
82
91
 
83
92
  it "delegates to the adapter" do
84
93
  expect_any_instance_of(
85
94
  Lita::Adapters::Shell
86
95
  ).to receive(:send_messages).with(
87
- source, ["foo", "bar"]
96
+ source, %w(foo bar)
88
97
  )
89
98
  subject.send_messages(source, "foo", "bar")
90
99
  end
91
100
  end
92
101
 
93
102
  describe "#set_topic" do
94
- let(:source) { double("Source") }
103
+ let(:source) { instance_double("Lita::Source") }
95
104
 
96
105
  it "delegates to the adapter" do
97
106
  expect_any_instance_of(Lita::Adapters::Shell).to receive(:set_topic).with(
@@ -55,7 +55,7 @@ describe handler_class, lita_handler: true do
55
55
  describe "#blah" do
56
56
  it "replies with bongo and wongo" do
57
57
  send_command("blah")
58
- expect(replies).to eq(["bongo", "wongo"])
58
+ expect(replies).to eq(%w(bongo wongo))
59
59
  end
60
60
  end
61
61
 
@@ -25,20 +25,4 @@ describe Lita::Source do
25
25
  it "requires either a user or a room" do
26
26
  expect { described_class.new }.to raise_error(ArgumentError)
27
27
  end
28
-
29
- describe "the deprecated Source.new(user, room) API" do
30
- it "can have a user and is marked as private if there is no room" do
31
- expect(Lita.logger).to receive(:warn)
32
- subject = described_class.new("Carl")
33
- expect(subject.user).to eq("Carl")
34
- expect(subject).to be_a_private_message
35
- end
36
-
37
- it "can have a room and is not marked as private if it does" do
38
- expect(Lita.logger).to receive(:warn)
39
- subject = described_class.new("Carl", "#litabot")
40
- expect(subject.room).to eq("#litabot")
41
- expect(subject).not_to be_a_private_message
42
- end
43
- end
44
28
  end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Timer do
4
+ let(:queue) { Queue.new }
5
+
6
+ before { allow(subject).to receive(:sleep) }
7
+
8
+ after { subject.stop }
9
+
10
+ it "runs single timers" do
11
+ subject = described_class.new { queue.push(true) }
12
+ expect(subject).to receive(:sleep).with(0).once
13
+ subject.start
14
+ expect(queue.pop(true)).to be(true)
15
+ expect { queue.pop(true) }.to raise_error(ThreadError)
16
+ end
17
+
18
+ it "runs recurring timers" do
19
+ halt = false
20
+ subject = described_class.new(interval: 1, recurring: true) do |timer|
21
+ queue.push(true)
22
+ timer.stop if halt
23
+ halt = true
24
+ end
25
+ expect(subject).to receive(:sleep).with(1).twice
26
+ subject.start
27
+ 2.times { expect(queue.pop(true)).to be(true) }
28
+ expect { queue.pop(true) }.to raise_error(ThreadError)
29
+ end
30
+ end
@@ -9,12 +9,9 @@ describe Lita::User, lita: true do
9
9
  persisted_user = described_class.find(1)
10
10
  expect(user).to eq(persisted_user)
11
11
  end
12
- end
13
-
14
- describe ".find" do
15
- before { described_class.create(1, name: "Carl") }
16
12
 
17
13
  it "returns existing users" do
14
+ described_class.create(1, name: "Carl")
18
15
  expect_any_instance_of(described_class).not_to receive(:save)
19
16
  user = described_class.find(1, name: "Carl")
20
17
  expect(user.id).to eq("1")
@@ -22,6 +19,26 @@ describe Lita::User, lita: true do
22
19
  end
23
20
  end
24
21
 
22
+ describe ".find_by_id" do
23
+ it "finds users with no metadata stored" do
24
+ described_class.create(1)
25
+ user = described_class.find_by_id(1)
26
+ expect(user.id).to eq("1")
27
+ end
28
+ end
29
+
30
+ describe ".find_by_mention_name" do
31
+ it "returns nil if no user matches the provided mention name" do
32
+ expect(described_class.find_by_mention_name("carlthepug")).to be_nil
33
+ end
34
+
35
+ it "returns a user that matches the provided mention name" do
36
+ described_class.create(1, mention_name: "carlthepug")
37
+ user = described_class.find_by_mention_name("carlthepug")
38
+ expect(user.id).to eq("1")
39
+ end
40
+ end
41
+
25
42
  describe ".find_by_name" do
26
43
  it "returns nil if no user matches the provided name" do
27
44
  expect(described_class.find_by_name("Carl")).to be_nil
@@ -34,18 +51,61 @@ describe Lita::User, lita: true do
34
51
  end
35
52
  end
36
53
 
54
+ describe ".find_by_partial_name" do
55
+ before { described_class.create(1, name: "José Vicente Cuadra") }
56
+
57
+ it "finds users by partial name match" do
58
+ user = described_class.find_by_partial_name("José")
59
+ expect(user.id).to eq("1")
60
+ end
61
+
62
+ it "returns nil if no users' names start with the provided string" do
63
+ expect(described_class.find_by_partial_name("Foo")).to be_nil
64
+ end
65
+
66
+ it "returns nil if more than one match was found" do
67
+ described_class.create(2, name: "José Contreras")
68
+ expect(described_class.find_by_partial_name("José")).to be_nil
69
+ end
70
+ end
71
+
72
+ describe ".fuzzy_find" do
73
+ let!(:user) { described_class.create(1, name: "Carl the Pug", mention_name: "carlthepug") }
74
+
75
+ it "finds by ID" do
76
+ expect(described_class.fuzzy_find(1)).to eq(user)
77
+ end
78
+
79
+ it "finds by mention name" do
80
+ expect(described_class.fuzzy_find("carlthepug")).to eq(user)
81
+ end
82
+
83
+ it "finds by name" do
84
+ expect(described_class.fuzzy_find("Carl the Pug")).to eq(user)
85
+ end
86
+
87
+ it "finds by partial mention name" do
88
+ expect(described_class.fuzzy_find("Carl")).to eq(user)
89
+ end
90
+ end
91
+
37
92
  describe "#save" do
38
- subject { described_class.new(1, name: "Carl") }
93
+ subject { described_class.new(1, name: "Carl", mention_name: "carlthepug") }
39
94
 
40
95
  it "saves an ID to name mapping for the user in Redis" do
41
96
  subject.save
42
- expect(described_class.redis.hgetall("id:1")).to eq("name" => "Carl")
97
+ expect(described_class.redis.hgetall("id:1")).to include("name" => "Carl")
43
98
  end
44
99
 
45
100
  it "saves a name to ID mapping for the user in Redis" do
46
101
  subject.save
47
102
  expect(described_class.redis.get("name:Carl")).to eq("1")
48
103
  end
104
+
105
+ it "saves a mention name to ID mapping for the user in Redis" do
106
+ subject.save
107
+ expect(described_class.redis.get("mention_name:carlthepug")).to eq("1")
108
+ end
49
109
  end
50
110
 
51
111
  describe "#==" do
@@ -13,6 +13,43 @@ describe Lita do
13
13
  end
14
14
  end
15
15
 
16
+ describe ".load_locales" do
17
+ let(:load_path) do
18
+ load_path = double("Array")
19
+ allow(load_path).to receive(:concat)
20
+ load_path
21
+ end
22
+
23
+ let(:new_locales) { %w(foo bar) }
24
+
25
+ before do
26
+ allow(I18n).to receive(:load_path).and_return(load_path)
27
+ allow(I18n).to receive(:reload!)
28
+ end
29
+
30
+ it "appends the locale files to I18n.load_path" do
31
+ expect(I18n.load_path).to receive(:concat).with(new_locales)
32
+ described_class.load_locales(new_locales)
33
+ end
34
+
35
+ it "reloads I18n" do
36
+ expect(I18n).to receive(:reload!)
37
+ described_class.load_locales(new_locales)
38
+ end
39
+
40
+ it "wraps single paths in an array" do
41
+ expect(I18n.load_path).to receive(:concat).with(["foo"])
42
+ described_class.load_locales("foo")
43
+ end
44
+ end
45
+
46
+ describe ".locale=" do
47
+ it "sets I18n.locale to the normalized locale" do
48
+ expect(I18n).to receive(:locale=).with("es-MX.UTF-8")
49
+ described_class.locale = "es_MX.UTF-8"
50
+ end
51
+ end
52
+
16
53
  describe ".redis" do
17
54
  it "memoizes a Redis::Namespace" do
18
55
  expect(described_class.redis).to respond_to(:namespace)
@@ -8,4 +8,15 @@ SimpleCov.start { add_filter "/spec/" }
8
8
 
9
9
  require "pry"
10
10
  require "lita"
11
+ require "lita/cli"
11
12
  require "lita/rspec"
13
+
14
+ RSpec.configure do |config|
15
+ config.mock_with :rspec do |mocks_config|
16
+ mocks_config.verify_doubled_constant_names = true
17
+ # Enable config option this when a new rspec-mocks beta including this patch is released:
18
+ # https://github.com/rspec/rspec-mocks/pull/466
19
+ #
20
+ # mocks_config.verify_partial_doubles = true
21
+ end
22
+ end
@@ -0,0 +1,90 @@
1
+ en:
2
+ lita:
3
+ adapters:
4
+ shell:
5
+ startup_message: Type "exit" or "quit" to end the session.
6
+ handlers:
7
+ authorization:
8
+ help:
9
+ add_key: auth add USER GROUP
10
+ add_value: Add USER to authorization group GROUP. Requires admin privileges.
11
+ remove_key: auth remove USER GROUP
12
+ remove_value: Remove USER from authorization group GROUP. Requires admin privileges.
13
+ list_key: "auth list [GROUP]"
14
+ list_value: >-
15
+ List authorization groups and the users in them. If GROUP is supplied,
16
+ only lists that group.
17
+ user_added: "%{user} was added to %{group}."
18
+ user_already_in: "%{user} was already in %{group}."
19
+ user_removed: "%{user} was removed from %{group}."
20
+ user_not_in: "%{user} was not in %{group}."
21
+ empty_state: There are no authorization groups yet.
22
+ empty_state_group: "There is no authorization group named %{group}."
23
+ format: "Format"
24
+ admin_management: Administrators can only be managed via Lita config.
25
+ no_user_found: 'No user was found with the identifier "%{identifier}".'
26
+ help:
27
+ help:
28
+ help_value: Lists help information for terms and command the robot will respond to.
29
+ help_command_key: help COMMAND
30
+ help_command_value: Lists help information for terms or commands that begin with COMMAND.
31
+ info:
32
+ help:
33
+ info_value: Replies with the current version of Lita.
34
+ room:
35
+ help:
36
+ join_key: join ROOM_ID
37
+ join_value: Makes the robot join the room with room ID ROOM_ID.
38
+ part_key: part ROOM_ID
39
+ part_value: Makes the robot part from the room with room ID ROOM_ID.
40
+ adapter:
41
+ name_required: Adapters that are anonymous classes must define self.name.
42
+ method_not_implemented: "This adapter has not implemented #%{method}."
43
+ missing_configs: "The following keys are required on config.adapter: %{configs}"
44
+ cli:
45
+ no_gemfile_warning: >-
46
+ The default command "start" must be run inside a Lita project. Try running `lita new` to
47
+ generate a new Lita project or `lita help` to see all commands.
48
+ travis_question: Do you want to test your plugin on Travis CI?
49
+ coveralls_question: |-
50
+ Do you want to generate code coverage information with SimpleCov and Coveralls.io?
51
+ config:
52
+ exception: |
53
+ Lita configuration file could not be processed. The exception was:
54
+ %{message}
55
+ Full backtrace:
56
+ %{backtrace}
57
+ daemon:
58
+ pid_exists: >-
59
+ PID file exists at %{path}. Lita may already be running. Kill the existing process or
60
+ remove the PID file and then start Lita.
61
+ kill_failure: "Failed to kill existing Lita process %{pid}."
62
+ handler:
63
+ name_required: Handlers that are anonymous classes must define self.name.
64
+ dispatch: "Dispatching message to %{handler}#%{method}."
65
+ exception: |
66
+ %{handler} crashed. The exception was:
67
+ %{message}
68
+ Full backtrace:
69
+ %{backtrace}
70
+ robot:
71
+ unknown_adapter: "Unknown adapter: :%{adapter}."
72
+ rspec:
73
+ full_suite_required: Lita::RSpec requires both RSpec::Mocks and RSpec::Expectations.
74
+ mocks_expect_syntax_required: RSpec::Mocks 2.14 or greater is required to use Lita::RSpec.
75
+ route_failure: |-
76
+ Expected message "%{message}" to route to :%{route}, but didn't.
77
+ negative_route_failure: |-
78
+ Expected message "%{message}" not to route to :%{route}, but did.
79
+ http_route_failure: |-
80
+ Expected request "%{method} %{path}" to route to :%{route}, but didn't.
81
+ negative_http_route_failure: |-
82
+ Expected request "%{method} %{path}" not to route to :%{route}, but did.
83
+ event_subscription_failure: |-
84
+ Expected triggering event "%{event}" to invoke :%{route}, but didn't.
85
+ negative_event_subscription_failure: |-
86
+ Expected triggering event "%{event}" not to invoke :%{route}, but did.
87
+ source:
88
+ user_or_room_required: Either a user or a room is required.
89
+ user:
90
+ find_deprecated: Lita::User.find will be removed in Lita 4.0. Use Lita::User.create instead.
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -12,3 +12,7 @@ module Lita
12
12
  <%- end -%>
13
13
  end
14
14
  end
15
+
16
+ Lita.load_locales Dir[File.expand_path(
17
+ File.join("..", "..", "..", "..", "locales", "*.yml"), __FILE__
18
+ )]