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,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
+ )]