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.
- 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
|