lita 2.7.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +26 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +6 -470
- data/Rakefile +3 -3
- data/lib/lita.rb +27 -19
- data/lib/lita/adapter.rb +46 -5
- data/lib/lita/adapters/shell.rb +18 -13
- data/lib/lita/authorization.rb +1 -1
- data/lib/lita/cli.rb +37 -23
- data/lib/lita/common.rb +35 -0
- data/lib/lita/config.rb +33 -13
- data/lib/lita/daemon.rb +15 -12
- data/lib/lita/handler.rb +49 -9
- data/lib/lita/handlers/authorization.rb +47 -47
- data/lib/lita/handlers/help.rb +16 -17
- data/lib/lita/handlers/info.rb +38 -0
- data/lib/lita/handlers/room.rb +32 -0
- data/lib/lita/http_route.rb +30 -19
- data/lib/lita/message.rb +3 -6
- data/lib/lita/rack_app.rb +11 -89
- data/lib/lita/response.rb +5 -15
- data/lib/lita/robot.rb +26 -10
- data/lib/lita/rspec.rb +6 -8
- data/lib/lita/rspec/handler.rb +49 -121
- data/lib/lita/rspec/matchers/event_subscription_matcher.rb +67 -0
- data/lib/lita/rspec/matchers/http_route_matcher.rb +72 -0
- data/lib/lita/rspec/matchers/route_matcher.rb +69 -0
- data/lib/lita/source.rb +5 -18
- data/lib/lita/timer.rb +45 -0
- data/lib/lita/user.rb +51 -4
- data/lib/lita/util.rb +5 -5
- data/lib/lita/version.rb +1 -1
- data/lita.gemspec +6 -3
- data/spec/lita/adapter_spec.rb +10 -2
- data/spec/lita/adapters/shell_spec.rb +3 -3
- data/spec/lita/authorization_spec.rb +11 -11
- data/spec/lita/config_spec.rb +8 -0
- data/spec/lita/daemon_spec.rb +65 -0
- data/spec/lita/handler_spec.rb +50 -11
- data/spec/lita/handlers/authorization_spec.rb +1 -1
- data/spec/lita/handlers/info_spec.rb +31 -0
- data/spec/lita/handlers/room_spec.rb +20 -0
- data/spec/lita/logger_spec.rb +1 -1
- data/spec/lita/message_spec.rb +4 -4
- data/spec/lita/rack_app_spec.rb +92 -0
- data/spec/lita/response_spec.rb +17 -8
- data/spec/lita/robot_spec.rb +23 -14
- data/spec/lita/rspec_spec.rb +1 -1
- data/spec/lita/source_spec.rb +0 -16
- data/spec/lita/timer_spec.rb +30 -0
- data/spec/lita/user_spec.rb +66 -6
- data/spec/lita_spec.rb +37 -0
- data/spec/spec_helper.rb +11 -0
- data/templates/locales/en.yml +90 -0
- data/templates/plugin/Rakefile +1 -1
- data/templates/plugin/lib/lita/plugin_type/plugin.tt +4 -0
- data/templates/plugin/locales/en.yml.tt +4 -0
- metadata +77 -18
- data/lib/lita/handlers/web.rb +0 -25
- data/spec/lita/handlers/web_spec.rb +0 -19
@@ -0,0 +1,67 @@
|
|
1
|
+
module Lita
|
2
|
+
module RSpec
|
3
|
+
# A namespace to hold all of Lita's RSpec matchers.
|
4
|
+
module Matchers
|
5
|
+
# Used to complete an event subscription test chain.
|
6
|
+
class EventSubscriptionMatcher
|
7
|
+
attr_accessor :context, :event_name, :expectation
|
8
|
+
attr_reader :expected_route
|
9
|
+
|
10
|
+
def initialize(context, event_name, expectation: true)
|
11
|
+
self.context = context
|
12
|
+
self.event_name = event_name
|
13
|
+
self.expectation = expectation
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sets an expectation that a handler method will or will not be called in
|
17
|
+
# response to a triggered event, then triggers the event.
|
18
|
+
# @param target_method_name [String, Symbol] The name of the method that
|
19
|
+
# should or should not be triggered.
|
20
|
+
# @return [void]
|
21
|
+
def to(target_method_name)
|
22
|
+
self.expected_route = target_method_name
|
23
|
+
|
24
|
+
e = expectation
|
25
|
+
ev = event_name
|
26
|
+
i = i18n_key
|
27
|
+
|
28
|
+
context.instance_eval do
|
29
|
+
called = false
|
30
|
+
allow(subject).to receive(target_method_name) { called = true }
|
31
|
+
robot.trigger(ev)
|
32
|
+
expect(called).to be(e), I18n.t(i, event: ev, route: target_method_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def description_prefix
|
39
|
+
if expectation
|
40
|
+
"routes event"
|
41
|
+
else
|
42
|
+
"doesn't route event"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def expected_route=(route)
|
47
|
+
@expected_route = route
|
48
|
+
set_description
|
49
|
+
end
|
50
|
+
|
51
|
+
def i18n_key
|
52
|
+
if expectation
|
53
|
+
"lita.rspec.event_subscription_failure"
|
54
|
+
else
|
55
|
+
"lita.rspec.negative_event_subscription_failure"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_description
|
60
|
+
description = %{#{description_prefix} "#{event_name}"}
|
61
|
+
description << " to :#{expected_route}" if expected_route
|
62
|
+
::RSpec.current_example.metadata[:description] = description
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Lita
|
2
|
+
module RSpec
|
3
|
+
# A namespace to hold all of Lita's RSpec matchers.
|
4
|
+
module Matchers
|
5
|
+
# Used to complete an HTTP routing test chain.
|
6
|
+
class HTTPRouteMatcher
|
7
|
+
attr_accessor :context, :http_method, :expectation, :path
|
8
|
+
attr_reader :expected_route
|
9
|
+
|
10
|
+
def initialize(context, http_method, path, expectation: true)
|
11
|
+
self.context = context
|
12
|
+
self.http_method = http_method
|
13
|
+
self.path = path
|
14
|
+
self.expectation = expectation
|
15
|
+
set_description
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets an expectation that an HTTP route will or will not be triggered,
|
19
|
+
# then makes an HTTP request against the app with the HTTP request
|
20
|
+
# method and path originally provided.
|
21
|
+
# @param route [Symbol] The name of the method that should or should not
|
22
|
+
# be triggered.
|
23
|
+
# @return [void]
|
24
|
+
def to(route)
|
25
|
+
self.expected_route = route
|
26
|
+
|
27
|
+
e = expectation
|
28
|
+
m = http_method.upcase
|
29
|
+
p = path
|
30
|
+
i = i18n_key
|
31
|
+
|
32
|
+
context.instance_eval do
|
33
|
+
called = false
|
34
|
+
allow(subject).to receive(route) { called = true }
|
35
|
+
env = Rack::MockRequest.env_for(p, method: m)
|
36
|
+
robot.app.call(env)
|
37
|
+
expect(called).to be(e), I18n.t(i, method: m, path: p, route: route)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def description_prefix
|
44
|
+
if expectation
|
45
|
+
"routes"
|
46
|
+
else
|
47
|
+
"doesn't route"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def expected_route=(route)
|
52
|
+
@expected_route = route
|
53
|
+
set_description
|
54
|
+
end
|
55
|
+
|
56
|
+
def i18n_key
|
57
|
+
if expectation
|
58
|
+
"lita.rspec.http_route_failure"
|
59
|
+
else
|
60
|
+
"lita.rspec.negative_http_route_failure"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_description
|
65
|
+
description = "#{description_prefix} #{http_method.upcase} #{path}"
|
66
|
+
description << " to :#{expected_route}" if expected_route
|
67
|
+
::RSpec.current_example.metadata[:description] = description
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Lita
|
2
|
+
module RSpec
|
3
|
+
# A namespace to hold all of Lita's RSpec matchers.
|
4
|
+
module Matchers
|
5
|
+
# Used to complete a chat routing test chain.
|
6
|
+
class RouteMatcher
|
7
|
+
attr_accessor :context, :expectation, :message_body
|
8
|
+
attr_reader :expected_route
|
9
|
+
|
10
|
+
def initialize(context, message_body, expectation: true)
|
11
|
+
self.context = context
|
12
|
+
self.message_body = message_body
|
13
|
+
self.expectation = expectation
|
14
|
+
set_description
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets an expectation that a route will or will not be triggered, then
|
18
|
+
# sends the message originally provided.
|
19
|
+
# @param route [Symbol] The name of the method that should or should not
|
20
|
+
# be triggered.
|
21
|
+
# @return [void]
|
22
|
+
def to(route)
|
23
|
+
self.expected_route = route
|
24
|
+
|
25
|
+
e = expectation
|
26
|
+
b = message_body
|
27
|
+
i = i18n_key
|
28
|
+
|
29
|
+
context.instance_eval do
|
30
|
+
allow(Authorization).to receive(:user_in_group?).and_return(true)
|
31
|
+
called = false
|
32
|
+
allow(subject).to receive(route) { called = true }
|
33
|
+
send_message(b)
|
34
|
+
expect(called).to be(e), I18n.t(i, message: b, route: route)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def description_prefix
|
41
|
+
if expectation
|
42
|
+
"routes"
|
43
|
+
else
|
44
|
+
"doesn't route"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def expected_route=(route)
|
49
|
+
@expected_route = route
|
50
|
+
set_description
|
51
|
+
end
|
52
|
+
|
53
|
+
def i18n_key
|
54
|
+
if expectation
|
55
|
+
"lita.rspec.route_failure"
|
56
|
+
else
|
57
|
+
"lita.rspec.negative_route_failure"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_description
|
62
|
+
description = %{#{description_prefix} "#{message_body}"}
|
63
|
+
description << " to :#{expected_route}" if expected_route
|
64
|
+
::RSpec.current_example.metadata[:description] = description
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/lita/source.rb
CHANGED
@@ -27,25 +27,12 @@ module Lita
|
|
27
27
|
# this string will differ depending on the chat service.
|
28
28
|
# @param private_message [Boolean] A flag indicating whether or not the
|
29
29
|
# message was sent privately.
|
30
|
-
def initialize(
|
31
|
-
|
30
|
+
def initialize(user: nil, room: nil, private_message: false)
|
31
|
+
@user = user
|
32
|
+
@room = room
|
33
|
+
@private_message = private_message
|
32
34
|
|
33
|
-
|
34
|
-
@room = options[:room]
|
35
|
-
@private_message = options[:private_message]
|
36
|
-
|
37
|
-
if args.size > 0
|
38
|
-
Lita.logger.warn <<-WARNING.chomp
|
39
|
-
Passing positional arguments to Source is deprecated. \
|
40
|
-
Use Source.new(user: user, room: room, private_message: pm) instead.
|
41
|
-
WARNING
|
42
|
-
@user = args[0] if args[0]
|
43
|
-
@room = args[1] if args[1]
|
44
|
-
end
|
45
|
-
|
46
|
-
if user.nil? && room.nil?
|
47
|
-
raise ArgumentError.new("Either a user or a room is required.")
|
48
|
-
end
|
35
|
+
raise ArgumentError, I18n.t("lita.source.user_or_room_required") if user.nil? && room.nil?
|
49
36
|
|
50
37
|
@private_message = true if room.nil?
|
51
38
|
end
|
data/lib/lita/timer.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Lita
|
2
|
+
# A timer that executes a block after a certain number of seconds, either once or repeatedly.
|
3
|
+
# @since 3.0.0
|
4
|
+
class Timer
|
5
|
+
# @param interval [Integer] The number of seconds to wait before calling the block.
|
6
|
+
# @param recurring [Boolean] If true, the timer will fire repeatedly until stopped.
|
7
|
+
# @yieldparam timer [Lita::Timer] The current {Lita::Timer} instance.
|
8
|
+
def initialize(interval: 0, recurring: false, &block)
|
9
|
+
@interval = interval
|
10
|
+
@recurring = recurring
|
11
|
+
@running = false
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Starts running the timer.
|
16
|
+
def start
|
17
|
+
@running = true
|
18
|
+
run
|
19
|
+
end
|
20
|
+
|
21
|
+
# Stops the timer, preventing any further invocations of the block until started again.
|
22
|
+
def stop
|
23
|
+
@running = false
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Is this a recurring timer?
|
29
|
+
def recurring?
|
30
|
+
@recurring
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sleep for the given interval, call the block, then run again if it's a recurring timer.
|
34
|
+
def run
|
35
|
+
sleep @interval
|
36
|
+
@block.call(self) if running? && @block
|
37
|
+
run if running? && recurring?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Is the timer currently running?
|
41
|
+
def running?
|
42
|
+
@running
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/lita/user.rb
CHANGED
@@ -22,14 +22,28 @@ module Lita
|
|
22
22
|
end
|
23
23
|
user
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
|
+
# @deprecated Use {.create} instead.
|
27
|
+
def find(id, metadata = {})
|
28
|
+
Lita.logger.warn I18n.t("lita.user.find_deprecated")
|
29
|
+
create(id, metadata)
|
30
|
+
end
|
26
31
|
|
27
32
|
# Finds a user by ID.
|
28
33
|
# @param id [Integer, String] The user's unique ID.
|
29
34
|
# @return [Lita::User, nil] The user or +nil+ if no such user is known.
|
30
35
|
def find_by_id(id)
|
31
36
|
metadata = redis.hgetall("id:#{id}")
|
32
|
-
|
37
|
+
new(id, metadata) if metadata.key?("name")
|
38
|
+
end
|
39
|
+
|
40
|
+
# Finds a user by mention name.
|
41
|
+
# @param mention_name [String] The user's mention name.
|
42
|
+
# @return [Lita::User, nil] The user or +nil+ if no such user is known.
|
43
|
+
# @since 3.0.0
|
44
|
+
def find_by_mention_name(mention_name)
|
45
|
+
id = redis.get("mention_name:#{mention_name}")
|
46
|
+
find_by_id(id) if id
|
33
47
|
end
|
34
48
|
|
35
49
|
# Finds a user by display name.
|
@@ -39,6 +53,28 @@ module Lita
|
|
39
53
|
id = redis.get("name:#{name}")
|
40
54
|
find_by_id(id) if id
|
41
55
|
end
|
56
|
+
|
57
|
+
# Attempts to find a user with a name starting with the provided string.
|
58
|
+
# @param name [String] The first characters in the user's name.
|
59
|
+
# @return [Lita::User, nil] The user, or +nil+ if zero or greater than 1 matches were found.
|
60
|
+
# @since 3.0.0
|
61
|
+
def find_by_partial_name(name)
|
62
|
+
keys = redis.keys("name:#{name}*")
|
63
|
+
|
64
|
+
if keys.length == 1
|
65
|
+
id = redis.get(keys.first)
|
66
|
+
find_by_id(id)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Finds a user by ID, mention name, name, or partial name.
|
71
|
+
# @param identifier [String] The user's ID, name, partial name, or mention name.
|
72
|
+
# @return [Lita::User, nil] The user or +nil+ if no users were found.
|
73
|
+
# @since 3.0.0
|
74
|
+
def fuzzy_find(identifier)
|
75
|
+
find_by_id(identifier) || find_by_mention_name(identifier) ||
|
76
|
+
find_by_name(identifier) || find_by_partial_name(identifier)
|
77
|
+
end
|
42
78
|
end
|
43
79
|
|
44
80
|
# The user's unique ID.
|
@@ -60,15 +96,19 @@ module Lita
|
|
60
96
|
@id = id.to_s
|
61
97
|
@metadata = metadata
|
62
98
|
@name = @metadata[:name] || @metadata["name"] || @id
|
99
|
+
ensure_name_metadata_set
|
63
100
|
end
|
64
101
|
|
65
102
|
# Saves the user record to Redis, overwriting an previous data for the
|
66
103
|
# current ID and user name.
|
67
104
|
# @return [void]
|
68
105
|
def save
|
106
|
+
mention_name = metadata[:mention_name] || metadata["mention_name"]
|
107
|
+
|
69
108
|
redis.pipelined do
|
70
109
|
redis.hmset("id:#{id}", *metadata.to_a.flatten)
|
71
110
|
redis.set("name:#{name}", id)
|
111
|
+
redis.set("mention_name:#{mention_name}", id) if mention_name
|
72
112
|
end
|
73
113
|
end
|
74
114
|
|
@@ -77,12 +117,19 @@ module Lita
|
|
77
117
|
# @param other (Lita::User) The user to compare against.
|
78
118
|
# @return [Boolean] True if users are equal, false otherwise.
|
79
119
|
def ==(other)
|
80
|
-
other.respond_to?(:id) && id == other.id &&
|
81
|
-
other.respond_to?(:name) && name == other.name
|
120
|
+
other.respond_to?(:id) && id == other.id && other.respond_to?(:name) && name == other.name
|
82
121
|
end
|
83
122
|
|
84
123
|
private
|
85
124
|
|
125
|
+
# Ensure the user's metadata contains their name, to ensure their Redis hash contains at least
|
126
|
+
# one value. It's not possible to store an empty hash key in Redis.
|
127
|
+
def ensure_name_metadata_set
|
128
|
+
username = metadata.delete("name")
|
129
|
+
username = metadata.delete(:name) unless username
|
130
|
+
metadata["name"] = username || id
|
131
|
+
end
|
132
|
+
|
86
133
|
# The Redis connection for user persistence.
|
87
134
|
def redis
|
88
135
|
self.class.redis
|
data/lib/lita/util.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Lita
|
2
|
-
# Handy utilities used by other
|
2
|
+
# Handy utilities used by other Lita classes.
|
3
3
|
module Util
|
4
4
|
# A regular expression for acronyms.
|
5
5
|
ACRONYM_REGEX = /(?=a)b/
|
@@ -11,12 +11,12 @@ module Lita
|
|
11
11
|
# @return [String] The transformed word.
|
12
12
|
def underscore(camel_cased_word)
|
13
13
|
word = camel_cased_word.to_s.dup
|
14
|
-
word.gsub!(
|
14
|
+
word.gsub!("::", "/")
|
15
15
|
word.gsub!(/(?:([A-Za-z\d])|^)(#{ACRONYM_REGEX})(?=\b|[^a-z])/) do
|
16
|
-
"#{
|
16
|
+
"#{Regexp.last_match[1]}#{Regexp.last_match[1] && '_'}#{Regexp.last_match[2].downcase}"
|
17
17
|
end
|
18
|
-
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
19
|
-
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
18
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
19
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
20
20
|
word.tr!("-", "_")
|
21
21
|
word.downcase!
|
22
22
|
word
|
data/lib/lita/version.rb
CHANGED
data/lita.gemspec
CHANGED
@@ -22,16 +22,19 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_runtime_dependency "bundler", ">= 1.3"
|
24
24
|
spec.add_runtime_dependency "faraday", ">= 0.8.7"
|
25
|
+
spec.add_runtime_dependency "http_router", ">= 0.11.1"
|
26
|
+
spec.add_runtime_dependency "ice_nine", ">= 0.11.0"
|
27
|
+
spec.add_runtime_dependency "i18n", ">= 0.6.9"
|
25
28
|
spec.add_runtime_dependency "multi_json", ">= 1.7.7"
|
29
|
+
spec.add_runtime_dependency "puma", ">= 2.7.1"
|
26
30
|
spec.add_runtime_dependency "rack", ">= 1.5.2"
|
27
31
|
spec.add_runtime_dependency "redis-namespace", ">= 1.3.0"
|
28
|
-
spec.add_runtime_dependency "thin", ">= 1.5.1"
|
29
32
|
spec.add_runtime_dependency "thor", ">= 0.18.1"
|
30
33
|
|
31
34
|
spec.add_development_dependency "rake"
|
32
|
-
spec.add_development_dependency "rspec", ">=
|
35
|
+
spec.add_development_dependency "rspec", ">= 3.0.0.beta1"
|
33
36
|
spec.add_development_dependency "simplecov"
|
34
37
|
spec.add_development_dependency "coveralls"
|
35
38
|
spec.add_development_dependency "pry"
|
36
|
-
spec.add_development_dependency "
|
39
|
+
spec.add_development_dependency "rubocop"
|
37
40
|
end
|
data/spec/lita/adapter_spec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Lita::Adapter do
|
4
|
-
let(:robot) {
|
4
|
+
let(:robot) { instance_double("Lita::Robot") }
|
5
5
|
|
6
|
-
let(:required_methods) { [:run, :send_messages, :set_topic, :shut_down] }
|
6
|
+
let(:required_methods) { [:join, :part, :run, :send_messages, :set_topic, :shut_down] }
|
7
7
|
|
8
8
|
subject { described_class.new(robot) }
|
9
9
|
|
@@ -34,4 +34,12 @@ describe Lita::Adapter do
|
|
34
34
|
expect { subject }.to raise_error(SystemExit)
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
describe ".namespace" do
|
39
|
+
it "raises an exception if self.name is not implemented" do
|
40
|
+
expect do
|
41
|
+
Class.new(Lita::Adapter).namespace
|
42
|
+
end.to raise_error(RuntimeError)
|
43
|
+
end
|
44
|
+
end
|
37
45
|
end
|