lita 3.3.1 → 4.0.0.rc1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +3 -0
  4. data/lib/lita.rb +45 -97
  5. data/lib/lita/adapter.rb +38 -17
  6. data/lib/lita/adapters/shell.rb +5 -3
  7. data/lib/lita/authorization.rb +109 -60
  8. data/lib/lita/builder.rb +38 -0
  9. data/lib/lita/callback.rb +37 -0
  10. data/lib/lita/cli.rb +2 -0
  11. data/lib/lita/config.rb +1 -18
  12. data/lib/lita/configurable.rb +29 -0
  13. data/lib/lita/configuration.rb +179 -0
  14. data/lib/lita/configuration_validator.rb +66 -0
  15. data/lib/lita/daemon.rb +4 -11
  16. data/lib/lita/default_configuration.rb +146 -0
  17. data/lib/lita/errors.rb +9 -0
  18. data/lib/lita/handler.rb +5 -264
  19. data/lib/lita/handler/chat_router.rb +130 -0
  20. data/lib/lita/handler/common.rb +114 -0
  21. data/lib/lita/handler/event_router.rb +77 -0
  22. data/lib/lita/handler/http_router.rb +26 -0
  23. data/lib/lita/handlers/authorization.rb +13 -18
  24. data/lib/lita/handlers/deprecation_check.rb +24 -0
  25. data/lib/lita/handlers/help.rb +5 -3
  26. data/lib/lita/handlers/info.rb +2 -2
  27. data/lib/lita/http_callback.rb +29 -0
  28. data/lib/lita/http_route.rb +41 -26
  29. data/lib/lita/namespace.rb +23 -0
  30. data/lib/lita/rack_app.rb +29 -2
  31. data/lib/lita/registry.rb +133 -0
  32. data/lib/lita/robot.rb +58 -20
  33. data/lib/lita/route_validator.rb +12 -4
  34. data/lib/lita/rspec.rb +23 -14
  35. data/lib/lita/rspec/handler.rb +93 -23
  36. data/lib/lita/rspec/matchers/chat_route_matcher.rb +48 -0
  37. data/lib/lita/rspec/matchers/deprecated.rb +36 -0
  38. data/lib/lita/rspec/matchers/event_route_matcher.rb +27 -0
  39. data/lib/lita/rspec/matchers/http_route_matcher.rb +18 -60
  40. data/lib/lita/user.rb +0 -6
  41. data/lib/lita/util.rb +1 -8
  42. data/lib/lita/version.rb +1 -1
  43. data/lita.gemspec +1 -0
  44. data/spec/lita/adapter_spec.rb +25 -7
  45. data/spec/lita/adapters/shell_spec.rb +24 -4
  46. data/spec/lita/authorization_spec.rb +57 -38
  47. data/spec/lita/builder_spec.rb +39 -0
  48. data/spec/lita/config_spec.rb +0 -24
  49. data/spec/lita/configuration_spec.rb +222 -0
  50. data/spec/lita/configuration_validator_spec.rb +112 -0
  51. data/spec/lita/daemon_spec.rb +2 -2
  52. data/spec/lita/default_configuration_spec.rb +254 -0
  53. data/spec/lita/handler/chat_router_spec.rb +192 -0
  54. data/spec/lita/handler/common_spec.rb +272 -0
  55. data/spec/lita/handler/event_router_spec.rb +54 -0
  56. data/spec/lita/handler/http_router_spec.rb +106 -0
  57. data/spec/lita/handler_spec.rb +20 -291
  58. data/spec/lita/handlers/authorization_spec.rb +9 -11
  59. data/spec/lita/handlers/deprecation_check_spec.rb +21 -0
  60. data/spec/lita/handlers/help_spec.rb +31 -9
  61. data/spec/lita/handlers/info_spec.rb +2 -2
  62. data/spec/lita/handlers/room_spec.rb +5 -3
  63. data/spec/lita/robot_spec.rb +30 -11
  64. data/spec/lita/rspec_spec.rb +71 -31
  65. data/spec/lita/user_spec.rb +2 -2
  66. data/spec/lita_spec.rb +62 -4
  67. data/spec/spec_helper.rb +7 -0
  68. data/templates/locales/en.yml +56 -4
  69. data/templates/plugin/gemspec.tt +1 -0
  70. data/templates/plugin/spec/spec_helper.tt +4 -0
  71. metadata +54 -8
  72. data/lib/lita/rspec/matchers/event_subscription_matcher.rb +0 -67
  73. data/lib/lita/rspec/matchers/route_matcher.rb +0 -69
  74. data/spec/lita/rack_app_spec.rb +0 -92
@@ -0,0 +1,48 @@
1
+ module Lita
2
+ module RSpec
3
+ module Matchers
4
+ # RSpec matchers for chat routes.
5
+ # @since 4.0.0
6
+ module ChatRouteMatcher
7
+ extend ::RSpec::Matchers::DSL
8
+
9
+ matcher :route do |message_body|
10
+ match do
11
+ message = Message.new(robot, message_body, source)
12
+
13
+ if defined?(@group) and @group.to_s.downcase == "admins"
14
+ robot.config.robot.admins = Array(robot.config.robot.admins) + [source.user.id]
15
+ elsif defined?(@group)
16
+ robot.auth.add_user_to_group!(source.user, @group)
17
+ end
18
+
19
+ matching_routes = described_class.routes.select do |route|
20
+ RouteValidator.new(described_class, route, message, robot).call
21
+ end
22
+
23
+ if defined?(@method_name)
24
+ matching_routes.any? { |route| route.callback.method_name == @method_name }
25
+ else
26
+ !matching_routes.empty?
27
+ end
28
+ end
29
+
30
+ chain :with_authorization_for do |group|
31
+ @group = group
32
+ end
33
+
34
+ chain :to do |method_name|
35
+ @method_name = method_name
36
+ end
37
+ end
38
+
39
+ # Sets an expectation that the provided message routes to a command.
40
+ # @param message_body [String] The body of the message.
41
+ # @return [void]
42
+ def route_command(message_body)
43
+ route("#{robot.mention_name} #{message_body}")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ module Lita
2
+ module RSpec
3
+ module Matchers
4
+ # Lita 3 versions of the routing matchers.
5
+ # @deprecated Will be removed in Lita 5.0. Use the +is_expected+ forms instead.
6
+ class Deprecated
7
+ # @param context [RSpec::ExampleGroup] The example group where the matcher was called.
8
+ # @param new_method_name [String, Symbol] The method that should be used instead.
9
+ # @param positive [Boolean] Whether or not a positive expectation is being made.
10
+ def initialize(context, new_method_name, positive, *args)
11
+ @context = context
12
+ @new_method_name = new_method_name
13
+ @expectation_method_name = positive ? :to : :not_to
14
+ @args = args
15
+
16
+ @context.instance_exec do
17
+ allow_any_instance_of(Authorization).to receive(:user_in_group?).and_return(true)
18
+ end
19
+ end
20
+
21
+ # Sets an expectation that the previously supplied message will route to the provided
22
+ # method.
23
+ # @param method_name [String, Symbol] The name of the method that should be routed to.
24
+ def to(method_name)
25
+ emn = @expectation_method_name
26
+ matcher = @context.public_send(@new_method_name, *@args)
27
+ matcher.to(method_name)
28
+
29
+ @context.instance_exec do
30
+ is_expected.public_send(emn, matcher)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ module Lita
2
+ module RSpec
3
+ module Matchers
4
+ # RSpec matchers for event routes.
5
+ # @since 4.0.0
6
+ module EventRouteMatcher
7
+ extend ::RSpec::Matchers::DSL
8
+
9
+ matcher :route_event do |event_name|
10
+ match do
11
+ callbacks = described_class.event_subscriptions_for(event_name)
12
+
13
+ if defined?(@method_name)
14
+ callbacks.any? { |callback| callback.method_name.equal?(@method_name) }
15
+ else
16
+ !callbacks.empty?
17
+ end
18
+ end
19
+
20
+ chain :to do |method_name|
21
+ @method_name = method_name
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,70 +2,28 @@ module Lita
2
2
  module RSpec
3
3
  # A namespace to hold all of Lita's RSpec matchers.
4
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"
5
+ # RSpec matchers for HTTP routes.
6
+ # @since 4.0.0
7
+ module HTTPRouteMatcher
8
+ extend ::RSpec::Matchers::DSL
9
+
10
+ matcher :route_http do |http_method, path|
11
+ match do
12
+ env = Rack::MockRequest.env_for(path, method: http_method)
13
+
14
+ matching_routes = robot.app.recognize(env)
15
+
16
+ if defined?(@method_name)
17
+ matching_routes.include?(@method_name)
18
+ else
19
+ !matching_routes.empty?
20
+ end
48
21
  end
49
- end
50
22
 
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"
23
+ chain :to do |method_name|
24
+ @method_name = method_name
61
25
  end
62
26
  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
27
  end
70
28
  end
71
29
  end
@@ -23,12 +23,6 @@ module Lita
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
31
-
32
26
  # Finds a user by ID.
33
27
  # @param id [Integer, String] The user's unique ID.
34
28
  # @return [Lita::User, nil] The user or +nil+ if no such user is known.
@@ -1,9 +1,6 @@
1
1
  module Lita
2
2
  # Handy utilities used by other Lita classes.
3
3
  module Util
4
- # A regular expression for acronyms.
5
- ACRONYM_REGEX = /(?=a)b/
6
-
7
4
  class << self
8
5
  # Returns a hash with any symbol keys converted to strings.
9
6
  # @param hash [Hash] The hash to convert.
@@ -14,16 +11,12 @@ module Lita
14
11
  result
15
12
  end
16
13
 
17
- # Transforms a camel-cased string into a snaked-cased string. Taken from
18
- # +ActiveSupport.+
14
+ # Transforms a camel-cased string into a snaked-cased string. Taken from +ActiveSupport.+
19
15
  # @param camel_cased_word [String] The word to transform.
20
16
  # @return [String] The transformed word.
21
17
  def underscore(camel_cased_word)
22
18
  word = camel_cased_word.to_s.dup
23
19
  word.gsub!("::", "/")
24
- word.gsub!(/(?:([A-Za-z\d])|^)(#{ACRONYM_REGEX})(?=\b|[^a-z])/) do
25
- "#{Regexp.last_match[1]}#{Regexp.last_match[1] && "_"}#{Regexp.last_match[2].downcase}"
26
- end
27
20
  word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
28
21
  word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
29
22
  word.tr!("-", "_")
@@ -1,4 +1,4 @@
1
1
  module Lita
2
2
  # The current version of Lita.
3
- VERSION = "3.3.1"
3
+ VERSION = "4.0.0.rc1"
4
4
  end
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency "thor", ">= 0.18.1"
34
34
 
35
35
  spec.add_development_dependency "rake"
36
+ spec.add_development_dependency "rack-test"
36
37
  spec.add_development_dependency "rspec", ">= 3.0.0"
37
38
  spec.add_development_dependency "simplecov"
38
39
  spec.add_development_dependency "coveralls"
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Lita::Adapter do
4
- let(:robot) { instance_double("Lita::Robot") }
3
+ describe Lita::Adapter, lita: true do
4
+ let(:robot) { Lita::Robot.new(registry) }
5
5
 
6
6
  let(:required_methods) { [:join, :part, :run, :send_messages, :set_topic, :shut_down] }
7
7
 
@@ -33,13 +33,31 @@ describe Lita::Adapter do
33
33
  expect(Lita.logger).to receive(:fatal).with(/foo, bar, baz, blah, bleh/)
34
34
  expect { subject }.to raise_error(SystemExit)
35
35
  end
36
+
37
+ it "logs a deprecation warning when the adapter is initialized" do
38
+ expect(Lita.logger).to receive(:warn).with(/Use Lita::Adapter\.config instead/)
39
+
40
+ expect { adapter_class.new(robot) }.to raise_error(SystemExit)
41
+ end
36
42
  end
37
43
 
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)
44
+ describe "#config" do
45
+ let(:adapter) do
46
+ Class.new(described_class) do
47
+ namespace "test"
48
+
49
+ config :foo, default: :bar
50
+ end
51
+ end
52
+
53
+ let(:robot) { Lita::Robot.new(registry) }
54
+
55
+ before { registry.register_adapter(:test, adapter) }
56
+
57
+ subject { adapter.new(robot) }
58
+
59
+ it "provides access to the adapter's configuration object" do
60
+ expect(subject.config.foo).to eq(:bar)
43
61
  end
44
62
  end
45
63
 
@@ -1,12 +1,21 @@
1
- describe Lita::Adapters::Shell do
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::Shell, lita: true do
2
4
  let(:robot) do
3
- instance_double("Lita::Robot", name: "Lita", mention_name: "LitaBot", alias: "/")
5
+ instance_double(
6
+ "Lita::Robot",
7
+ name: "Lita",
8
+ mention_name: "LitaBot",
9
+ alias: "/",
10
+ config: registry.config
11
+ )
4
12
  end
5
13
 
6
14
  subject { described_class.new(robot) }
7
15
 
8
16
  describe "#run" do
9
17
  before do
18
+ registry.register_adapter(:shell, described_class)
10
19
  allow(subject).to receive(:puts)
11
20
  allow(Readline).to receive(:readline).and_return("foo", "exit")
12
21
  allow(robot).to receive(:trigger)
@@ -19,8 +28,8 @@ describe Lita::Adapters::Shell do
19
28
  subject.run
20
29
  end
21
30
 
22
- it "marks messages as commands if config.adapter.private_chat is true" do
23
- Lita.config.adapter.private_chat = true
31
+ it "marks messages as commands if config.adapters.shell.private_chat is true" do
32
+ registry.config.adapters.shell.private_chat = true
24
33
  expect_any_instance_of(Lita::Message).to receive(:command!)
25
34
  subject.run
26
35
  end
@@ -29,6 +38,17 @@ describe Lita::Adapters::Shell do
29
38
  expect(robot).to receive(:trigger).with(:connected)
30
39
  subject.run
31
40
  end
41
+
42
+ it "exits cleanly when EOF is received" do
43
+ allow(Readline).to receive(:readline).and_return(nil)
44
+ subject.run
45
+ end
46
+
47
+ it "removes empty input from readline history" do
48
+ allow(Readline).to receive(:readline).and_return("", "exit")
49
+ expect(Readline::HISTORY).to receive(:pop)
50
+ subject.run
51
+ end
32
52
  end
33
53
 
34
54
  describe "#send_message" do
@@ -4,112 +4,131 @@ describe Lita::Authorization, lita: true do
4
4
  let(:requesting_user) { instance_double("Lita::User", id: "1") }
5
5
  let(:user) { instance_double("Lita::User", id: "2") }
6
6
 
7
+ subject { described_class.new(registry.config) }
8
+
7
9
  before do
8
- Lita.config.robot.admins = ["1"]
10
+ registry.config.robot.admins = ["1"]
9
11
  end
10
12
 
11
- describe ".add_user_to_group" do
13
+ describe "deprecated class methods" do
14
+ {
15
+ add_user_to_group: 3,
16
+ remove_user_from_group: 3,
17
+ user_in_group?: 2,
18
+ user_is_admin?: 1,
19
+ groups: 0,
20
+ groups_with_users: 0
21
+ }.each do |deprecated_method, arity|
22
+ it "logs a warning and calls the instance method version of .#{deprecated_method}" do
23
+ expect(Lita.logger).to receive(:warn).with(
24
+ /Lita::Authorization\.#{Regexp.escape(deprecated_method)} will be removed in Lita 5\.0/
25
+ )
26
+ expect_any_instance_of(described_class).to receive(deprecated_method)
27
+ described_class.public_send(deprecated_method, *arity.times.map { Object.new })
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "#add_user_to_group" do
12
33
  it "adds users to an auth group" do
13
- described_class.add_user_to_group(requesting_user, user, "employees")
14
- expect(described_class.user_in_group?(user, "employees")).to be true
34
+ subject.add_user_to_group(requesting_user, user, "employees")
35
+ expect(subject.user_in_group?(user, "employees")).to be true
15
36
  end
16
37
 
17
38
  it "can only be called by admins" do
18
- Lita.config.robot.admins = nil
19
- result = described_class.add_user_to_group(
39
+ registry.config.robot.admins = nil
40
+ result = subject.add_user_to_group(
20
41
  requesting_user,
21
42
  user,
22
43
  "employees"
23
44
  )
24
45
  expect(result).to eq(:unauthorized)
25
- expect(described_class.user_in_group?(user, "employees")).to be false
46
+ expect(subject.user_in_group?(user, "employees")).to be false
26
47
  end
27
48
 
28
49
  it "normalizes the group name" do
29
- described_class.add_user_to_group(requesting_user, user, "eMPLoYeeS")
30
- expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be true
50
+ subject.add_user_to_group(requesting_user, user, "eMPLoYeeS")
51
+ expect(subject.user_in_group?(user, " EmplOyEEs ")).to be true
31
52
  end
32
53
  end
33
54
 
34
- describe ".remove_user_from_group" do
55
+ describe "#remove_user_from_group" do
35
56
  it "removes users from an auth group" do
36
- described_class.add_user_to_group(requesting_user, user, "employees")
37
- described_class.remove_user_from_group(requesting_user, user, "employees")
38
- expect(described_class.user_in_group?(user, "employees")).to be false
57
+ subject.add_user_to_group(requesting_user, user, "employees")
58
+ subject.remove_user_from_group(requesting_user, user, "employees")
59
+ expect(subject.user_in_group?(user, "employees")).to be false
39
60
  end
40
61
 
41
62
  it "can only be called by admins" do
42
- described_class.add_user_to_group(requesting_user, user, "employees")
43
- Lita.config.robot.admins = nil
44
- result = described_class.remove_user_from_group(
63
+ subject.add_user_to_group(requesting_user, user, "employees")
64
+ registry.config.robot.admins = nil
65
+ result = subject.remove_user_from_group(
45
66
  requesting_user,
46
67
  user,
47
68
  "employees"
48
69
  )
49
70
  expect(result).to eq(:unauthorized)
50
- expect(described_class.user_in_group?(user, "employees")).to be true
71
+ expect(subject.user_in_group?(user, "employees")).to be true
51
72
  end
52
73
 
53
74
  it "normalizes the group name" do
54
- described_class.add_user_to_group(requesting_user, user, "eMPLoYeeS")
55
- described_class.remove_user_from_group(requesting_user, user, "EmployeeS")
56
- expect(described_class.user_in_group?(user, " EmplOyEEs ")).to be false
75
+ subject.add_user_to_group(requesting_user, user, "eMPLoYeeS")
76
+ subject.remove_user_from_group(requesting_user, user, "EmployeeS")
77
+ expect(subject.user_in_group?(user, " EmplOyEEs ")).to be false
57
78
  end
58
79
  end
59
80
 
60
- describe ".user_in_group?" do
81
+ describe "#user_in_group?" do
61
82
  it "returns false if the user is in the group" do
62
- expect(described_class.user_in_group?(user, "employees")).to be false
83
+ expect(subject.user_in_group?(user, "employees")).to be false
63
84
  end
64
85
 
65
86
  it "delegates to .user_is_admin? if the group is admins" do
66
- expect(described_class).to receive(:user_is_admin?)
67
- described_class.user_in_group?(user, "admins")
87
+ expect(subject).to receive(:user_is_admin?)
88
+ subject.user_in_group?(user, "admins")
68
89
  end
69
90
  end
70
91
 
71
- describe ".user_is_admin?" do
92
+ describe "#user_is_admin?" do
72
93
  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
94
+ expect(subject.user_is_admin?(requesting_user)).to be true
74
95
  end
75
96
 
76
97
  it "returns false if the user's ID is not in the config" do
77
- Lita.config.robot.admins = nil
78
- expect(described_class.user_is_admin?(user)).to be false
98
+ registry.config.robot.admins = nil
99
+ expect(subject.user_is_admin?(user)).to be false
79
100
  end
80
101
  end
81
102
 
82
- describe ".groups" do
103
+ describe "#groups" do
83
104
  before do
84
105
  %i(foo bar baz).each do |group|
85
- described_class.add_user_to_group(requesting_user, user, group)
106
+ subject.add_user_to_group(requesting_user, user, group)
86
107
  end
87
108
  end
88
109
 
89
110
  it "returns a list of all authorization groups" do
90
- expect(described_class.groups).to match_array(%i(foo bar baz))
111
+ expect(subject.groups).to match_array(%i(foo bar baz))
91
112
  end
92
113
  end
93
114
 
94
- describe ".groups_with_users" do
115
+ describe "#groups_with_users" do
95
116
  before do
96
117
  %i(foo bar baz).each do |group|
97
- described_class.add_user_to_group(requesting_user, user, group)
98
- described_class.add_user_to_group(
118
+ subject.add_user_to_group(requesting_user, user, group)
119
+ subject.add_user_to_group(
99
120
  requesting_user,
100
121
  requesting_user,
101
122
  group
102
123
  )
103
124
  end
104
- allow(Lita::User).to receive(:find_by_id).with("1").and_return(
105
- requesting_user
106
- )
125
+ allow(Lita::User).to receive(:find_by_id).with("1").and_return(requesting_user)
107
126
  allow(Lita::User).to receive(:find_by_id).with("2").and_return(user)
108
127
  end
109
128
 
110
129
  it "returns a hash of all authorization groups and their members" do
111
130
  groups = %i(foo bar baz)
112
- groups_with_users = described_class.groups_with_users
131
+ groups_with_users = subject.groups_with_users
113
132
  expect(groups_with_users.keys).to match_array(groups)
114
133
  groups.each do |group|
115
134
  expect(groups_with_users[group]).to match_array([user, requesting_user])