cased-ruby 0.3.3
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 +7 -0
- data/.github/workflows/rubocop.yml +21 -0
- data/.github/workflows/ruby.yml +46 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +107 -0
- data/LICENSE +21 -0
- data/README.md +661 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/rubocop +29 -0
- data/cased-ruby.gemspec +48 -0
- data/lib/cased-ruby.rb +3 -0
- data/lib/cased.rb +260 -0
- data/lib/cased/clients.rb +21 -0
- data/lib/cased/collection_response.rb +117 -0
- data/lib/cased/config.rb +172 -0
- data/lib/cased/context.rb +50 -0
- data/lib/cased/context/expander.rb +33 -0
- data/lib/cased/error.rb +8 -0
- data/lib/cased/http/client.rb +83 -0
- data/lib/cased/http/error.rb +99 -0
- data/lib/cased/instrumentation/controller.rb +34 -0
- data/lib/cased/instrumentation/log_subscriber.rb +31 -0
- data/lib/cased/integrations/sidekiq.rb +17 -0
- data/lib/cased/integrations/sidekiq/client_middleware.rb +14 -0
- data/lib/cased/integrations/sidekiq/server_middleware.rb +20 -0
- data/lib/cased/model.rb +98 -0
- data/lib/cased/policy.rb +24 -0
- data/lib/cased/publishers.rb +6 -0
- data/lib/cased/publishers/active_support_publisher.rb +16 -0
- data/lib/cased/publishers/base.rb +17 -0
- data/lib/cased/publishers/error.rb +11 -0
- data/lib/cased/publishers/http_publisher.rb +15 -0
- data/lib/cased/publishers/null_publisher.rb +11 -0
- data/lib/cased/publishers/test_publisher.rb +19 -0
- data/lib/cased/query.rb +87 -0
- data/lib/cased/rack_middleware.rb +15 -0
- data/lib/cased/response.rb +37 -0
- data/lib/cased/sensitive.rb +4 -0
- data/lib/cased/sensitive/handler.rb +54 -0
- data/lib/cased/sensitive/processor.rb +78 -0
- data/lib/cased/sensitive/range.rb +54 -0
- data/lib/cased/sensitive/result.rb +8 -0
- data/lib/cased/sensitive/string.rb +43 -0
- data/lib/cased/test_helper.rb +188 -0
- data/lib/cased/version.rb +5 -0
- data/vendor/cache/activesupport-6.0.3.4.gem +0 -0
- data/vendor/cache/addressable-2.7.0.gem +0 -0
- data/vendor/cache/ast-2.4.0.gem +0 -0
- data/vendor/cache/byebug-11.0.1.gem +0 -0
- data/vendor/cache/concurrent-ruby-1.1.7.gem +0 -0
- data/vendor/cache/connection_pool-2.2.2.gem +0 -0
- data/vendor/cache/crack-0.4.3.gem +0 -0
- data/vendor/cache/docile-1.3.2.gem +0 -0
- data/vendor/cache/dotpath-0.1.0.gem +0 -0
- data/vendor/cache/faraday-1.1.0.gem +0 -0
- data/vendor/cache/faraday_middleware-1.0.0.gem +0 -0
- data/vendor/cache/hashdiff-1.0.1.gem +0 -0
- data/vendor/cache/i18n-1.8.5.gem +0 -0
- data/vendor/cache/jaro_winkler-1.5.4.gem +0 -0
- data/vendor/cache/json-2.3.1.gem +0 -0
- data/vendor/cache/minitest-5.13.0.gem +0 -0
- data/vendor/cache/mocha-1.11.2.gem +0 -0
- data/vendor/cache/multipart-post-2.1.1.gem +0 -0
- data/vendor/cache/net-http-persistent-3.1.0.gem +0 -0
- data/vendor/cache/parallel-1.19.1.gem +0 -0
- data/vendor/cache/parser-2.7.1.3.gem +0 -0
- data/vendor/cache/public_suffix-4.0.5.gem +0 -0
- data/vendor/cache/rack-2.2.2.gem +0 -0
- data/vendor/cache/rack-protection-2.0.8.1.gem +0 -0
- data/vendor/cache/rainbow-3.0.0.gem +0 -0
- data/vendor/cache/rake-10.5.0.gem +0 -0
- data/vendor/cache/redis-4.1.4.gem +0 -0
- data/vendor/cache/rubocop-0.78.0.gem +0 -0
- data/vendor/cache/rubocop-performance-1.5.2.gem +0 -0
- data/vendor/cache/ruby-progressbar-1.10.1.gem +0 -0
- data/vendor/cache/ruby2_keywords-0.0.2.gem +0 -0
- data/vendor/cache/safe_yaml-1.0.5.gem +0 -0
- data/vendor/cache/sidekiq-6.0.7.gem +0 -0
- data/vendor/cache/simplecov-0.18.5.gem +0 -0
- data/vendor/cache/simplecov-html-0.12.2.gem +0 -0
- data/vendor/cache/thread_safe-0.3.6.gem +0 -0
- data/vendor/cache/tzinfo-1.2.7.gem +0 -0
- data/vendor/cache/unicode-display_width-1.6.1.gem +0 -0
- data/vendor/cache/webmock-3.8.3.gem +0 -0
- data/vendor/cache/yard-0.9.24.gem +0 -0
- data/vendor/cache/zeitwerk-2.4.0.gem +0 -0
- metadata +375 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cased
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :body
|
|
6
|
+
attr_reader :exception
|
|
7
|
+
|
|
8
|
+
def initialize(response: nil, exception: nil)
|
|
9
|
+
@response = response
|
|
10
|
+
@body = response&.body
|
|
11
|
+
@exception = exception
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def error
|
|
15
|
+
@exception.presence || (body && body['error']).presence
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def error?
|
|
19
|
+
# If there was an exception during the execution of the request.
|
|
20
|
+
return true if @exception.present?
|
|
21
|
+
|
|
22
|
+
# If the HTTP response was outside of 200-299
|
|
23
|
+
return true unless @response.success?
|
|
24
|
+
|
|
25
|
+
# If the HTTP response contained an error key.
|
|
26
|
+
return true if body && body['error'].present?
|
|
27
|
+
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def success?
|
|
32
|
+
return false if @response.nil?
|
|
33
|
+
|
|
34
|
+
@response.success?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cased
|
|
4
|
+
module Sensitive
|
|
5
|
+
class Handler
|
|
6
|
+
def self.handlers
|
|
7
|
+
@handlers ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
attr_writer :handlers
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.register(label, handler)
|
|
15
|
+
handlers << Handler.new(label, handler)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :label
|
|
19
|
+
|
|
20
|
+
def initialize(label, handler)
|
|
21
|
+
@label = label.to_sym
|
|
22
|
+
@handler = prepare_handler(handler)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call(audit_event, key, value)
|
|
26
|
+
@handler.call(audit_event, key.to_sym, value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def prepare_handler(handler)
|
|
32
|
+
case handler
|
|
33
|
+
when Regexp
|
|
34
|
+
proc do |_audit_event, key, value|
|
|
35
|
+
string = Cased::Sensitive::String.new(value)
|
|
36
|
+
string.matches(handler).collect do |match|
|
|
37
|
+
begin_offset = match.begin(0)
|
|
38
|
+
end_offset = match.end(0)
|
|
39
|
+
|
|
40
|
+
Cased::Sensitive::Range.new(
|
|
41
|
+
label: label,
|
|
42
|
+
key: key,
|
|
43
|
+
begin_offset: begin_offset,
|
|
44
|
+
end_offset: end_offset,
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "expected #{handler} to be a Regexp or Proc"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cased/sensitive/string'
|
|
4
|
+
|
|
5
|
+
module Cased
|
|
6
|
+
module Sensitive
|
|
7
|
+
class Processor
|
|
8
|
+
def self.process(audit_event, handlers = nil)
|
|
9
|
+
handlers ||= Cased::Sensitive::Handler.handlers
|
|
10
|
+
processor = new(audit_event, handlers)
|
|
11
|
+
processor.process
|
|
12
|
+
processor
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.process!(audit_event, handlers = nil)
|
|
16
|
+
processor = process(audit_event, handlers)
|
|
17
|
+
return unless processor.sensitive?
|
|
18
|
+
|
|
19
|
+
audit_event[:'.cased'] = {
|
|
20
|
+
pii: processor.to_h,
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :audit_event
|
|
25
|
+
attr_reader :handlers
|
|
26
|
+
|
|
27
|
+
def initialize(audit_event, handlers)
|
|
28
|
+
@audit_event = audit_event.dup.freeze
|
|
29
|
+
@ranges = []
|
|
30
|
+
@handlers = handlers
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def process
|
|
34
|
+
return true if defined?(@processed)
|
|
35
|
+
|
|
36
|
+
walk(audit_event)
|
|
37
|
+
@processed = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ranges
|
|
41
|
+
@ranges.flatten
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sensitive?
|
|
45
|
+
process && ranges.any?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_h
|
|
49
|
+
results = {}
|
|
50
|
+
ranges.each do |range|
|
|
51
|
+
results[range.key] ||= []
|
|
52
|
+
results[range.key] << range.to_h
|
|
53
|
+
end
|
|
54
|
+
results
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def walk(hash)
|
|
60
|
+
hash.each_with_json_path do |path, value|
|
|
61
|
+
case value
|
|
62
|
+
when Cased::Sensitive::String
|
|
63
|
+
@ranges << value.range(key: path)
|
|
64
|
+
when ::String
|
|
65
|
+
process_handlers(audit_event, path, value)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process_handlers(audit_event, path, value)
|
|
71
|
+
handlers.each do |handler|
|
|
72
|
+
ranges = handler.call(audit_event, path, value)
|
|
73
|
+
@ranges << ranges unless ranges.nil? || ranges.empty?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cased
|
|
4
|
+
module Sensitive
|
|
5
|
+
class Range
|
|
6
|
+
# Public: The human label describing what sensitive information was
|
|
7
|
+
# label. Username, email, date of birth, etc.
|
|
8
|
+
attr_reader :label
|
|
9
|
+
|
|
10
|
+
# Public: The JSON key.
|
|
11
|
+
attr_reader :key
|
|
12
|
+
|
|
13
|
+
# Public: This is the identifier that groups sensitive ranges together.
|
|
14
|
+
# This could be an identifier to an individual for example.
|
|
15
|
+
attr_reader :identifier
|
|
16
|
+
|
|
17
|
+
# Public: The beginning offset of the sensitive value in the original value.
|
|
18
|
+
attr_reader :begin_offset
|
|
19
|
+
|
|
20
|
+
# Public: The end offset of the sensitive value in the original value.
|
|
21
|
+
attr_reader :end_offset
|
|
22
|
+
|
|
23
|
+
def initialize(label: nil, key:, begin_offset:, end_offset:, identifier: nil)
|
|
24
|
+
raise ArgumentError, 'missing key' if key.nil?
|
|
25
|
+
raise ArgumentError, 'missing begin_offset' if begin_offset.nil?
|
|
26
|
+
raise ArgumentError, 'missing end_offset' if end_offset.nil?
|
|
27
|
+
|
|
28
|
+
@label = label
|
|
29
|
+
@key = key
|
|
30
|
+
@identifier = identifier
|
|
31
|
+
@begin_offset = begin_offset
|
|
32
|
+
@end_offset = end_offset
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ==(other)
|
|
36
|
+
@begin_offset == other.begin_offset &&
|
|
37
|
+
@end_offset == other.end_offset &&
|
|
38
|
+
@label == other.label &&
|
|
39
|
+
@key == other.key &&
|
|
40
|
+
@identifier == other.identifier
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_h
|
|
44
|
+
{
|
|
45
|
+
begin: @begin_offset,
|
|
46
|
+
end: @end_offset,
|
|
47
|
+
}.tap do |hash|
|
|
48
|
+
hash[:label] = label if label
|
|
49
|
+
hash[:identifier] = identifier if identifier
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cased/sensitive/range'
|
|
4
|
+
|
|
5
|
+
module Cased
|
|
6
|
+
module Sensitive
|
|
7
|
+
class String < String
|
|
8
|
+
attr_reader :label
|
|
9
|
+
attr_reader :string
|
|
10
|
+
|
|
11
|
+
def initialize(string, label: nil)
|
|
12
|
+
super(string)
|
|
13
|
+
@label = label
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def range(key:)
|
|
17
|
+
Cased::Sensitive::Range.new(
|
|
18
|
+
label: label,
|
|
19
|
+
key: key,
|
|
20
|
+
begin_offset: 0,
|
|
21
|
+
end_offset: length,
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def matches(regex)
|
|
26
|
+
offset = 0
|
|
27
|
+
matches = []
|
|
28
|
+
|
|
29
|
+
while (result = match(regex, offset))
|
|
30
|
+
matches.push(result)
|
|
31
|
+
offset = result.end(0)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
matches
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def ==(other)
|
|
38
|
+
super(other) &&
|
|
39
|
+
@label == other.label
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
|
4
|
+
|
|
5
|
+
module Cased
|
|
6
|
+
module TestHelper
|
|
7
|
+
def before_setup
|
|
8
|
+
@original_cased_publishers = Cased.publishers
|
|
9
|
+
Cased.publishers = [
|
|
10
|
+
cased_test_publisher,
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
clear_cased_events
|
|
14
|
+
clear_cased_context
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def after_teardown
|
|
19
|
+
super
|
|
20
|
+
|
|
21
|
+
Cased.publishers = @original_cased_publishers
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Clears all published events in the test Cased publisher
|
|
25
|
+
def clear_cased_events
|
|
26
|
+
cased_events.clear
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clear_cased_context
|
|
30
|
+
Cased::Context.clear!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cased_events
|
|
34
|
+
cased_test_publisher.events
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Assertion that helps with testing that a number of events have been published to Cased.
|
|
38
|
+
#
|
|
39
|
+
# @param expected_event_count [Integer] The number of expected Cased events to be published.
|
|
40
|
+
# @param expected_event_body [Hash] Expected event to be published to Cased.
|
|
41
|
+
#
|
|
42
|
+
# @example Expected events with a filter inside of a block
|
|
43
|
+
# def test_creates_user_create_event
|
|
44
|
+
# assert_cased_events 1, action: 'user.create' do
|
|
45
|
+
# create(:user)
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# @example Expected events without a filter inside of a block
|
|
50
|
+
# def test_creates_user_create_event
|
|
51
|
+
# assert_cased_events 1 do
|
|
52
|
+
# create(:user)
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# @example Expected events with a filter for the duration of the test
|
|
57
|
+
# def test_creates_user_create_event
|
|
58
|
+
# create(:user)
|
|
59
|
+
#
|
|
60
|
+
# assert_cased_events 1, action: 'user.create'
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# @example Expected events without a filter for the duration of the test
|
|
64
|
+
# def test_creates_user_create_event
|
|
65
|
+
# create(:user)
|
|
66
|
+
#
|
|
67
|
+
# assert_cased_events 1
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# @example Cased::Model value hash
|
|
71
|
+
# def test_creates_user_create_event
|
|
72
|
+
# user = create(:user)
|
|
73
|
+
#
|
|
74
|
+
# assert_cased_events 1, action: 'user.login', user: user
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# @return [void]
|
|
78
|
+
def assert_cased_events(expected_event_count, expected_event_body = nil, &block)
|
|
79
|
+
expected_event_body&.deep_symbolize_keys!
|
|
80
|
+
|
|
81
|
+
actual_event_count = if block
|
|
82
|
+
events_before_block = cased_events_with(expected_event_body)
|
|
83
|
+
|
|
84
|
+
block&.call
|
|
85
|
+
|
|
86
|
+
events_after_block = cased_events_with(expected_event_body)
|
|
87
|
+
|
|
88
|
+
events_after_block.length - events_before_block.length
|
|
89
|
+
else
|
|
90
|
+
cased_events_with(expected_event_body).length
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
assert_equal expected_event_count, actual_event_count, "#{expected_event_count} Cased published events expected, but #{actual_event_count} were published"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Assertion that expects there to have been zero matching Cased events.
|
|
97
|
+
#
|
|
98
|
+
# @param expected_event_body [Hash] Expected event not to be published to Cased.
|
|
99
|
+
#
|
|
100
|
+
# @example Expected no events with a filter inside of a block
|
|
101
|
+
# def test_creates_bot_account
|
|
102
|
+
# assert_no_cased_events action: 'bot.create' do
|
|
103
|
+
# create(:bot)
|
|
104
|
+
# end
|
|
105
|
+
# end
|
|
106
|
+
#
|
|
107
|
+
# @example Expected no events inside of a block
|
|
108
|
+
# def test_creates_bot_account
|
|
109
|
+
# assert_no_cased_events do
|
|
110
|
+
# create(:bot)
|
|
111
|
+
# end
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# @example Expected no events containing a subset of the event body for the duration of the test
|
|
115
|
+
# def test_creates_bot_account
|
|
116
|
+
# create(:bot)
|
|
117
|
+
#
|
|
118
|
+
# assert_no_cased_events action: 'bot.create'
|
|
119
|
+
# end
|
|
120
|
+
#
|
|
121
|
+
# @example Expected no events for the duration of the test
|
|
122
|
+
# def test_creates_bot_account
|
|
123
|
+
# create(:bot)
|
|
124
|
+
#
|
|
125
|
+
# assert_no_cased_events
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# @return [void]
|
|
129
|
+
def assert_no_cased_events(expected_event_body = nil, &block)
|
|
130
|
+
assert_cased_events(0, expected_event_body, &block)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Locates all published events matching a particular shape.
|
|
134
|
+
#
|
|
135
|
+
# @param expected_event [Hash] the shape of event expected to be published to Cased
|
|
136
|
+
#
|
|
137
|
+
# @example Simple hash
|
|
138
|
+
# cased_events_with(action: 'user.login') # => [{ action: 'user.login', actor: 'garrett@cased.com' }, { action: 'user.login', actor: 'ted@cased.com' }]
|
|
139
|
+
#
|
|
140
|
+
# @example Nested hash
|
|
141
|
+
# cased_events_with(issues: [{ issue_id: 1 }]) # => [{ action: 'user.login', issues: [{ issue_id: 1 }, { issue_id: 2 }]}]
|
|
142
|
+
#
|
|
143
|
+
# @example Cased::Model value hash
|
|
144
|
+
# user = User.new
|
|
145
|
+
# user.cased_context # => { user: 'garrett@cased.com', user_id: 'user_1234' }
|
|
146
|
+
# cased_events_with(user: user) # => [{ user: 'garrett@cased.com', user_id: 'user_1234' }]
|
|
147
|
+
#
|
|
148
|
+
# @return [Array<Hash>] Array of matching published Cased events.
|
|
149
|
+
# @raises [ArgumentError] if expected_event is empty.
|
|
150
|
+
def cased_events_with(expected_event = {})
|
|
151
|
+
return cased_events.dup if expected_event.nil?
|
|
152
|
+
|
|
153
|
+
if expected_event.empty?
|
|
154
|
+
raise ArgumentError, 'You must call cased_events_with with a non empty Hash otherwise it will match all events'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
expanded_expected_event = Cased::Context::Expander.expand(expected_event)
|
|
158
|
+
if expanded_expected_event.empty?
|
|
159
|
+
raise ArgumentError, <<~MSG.strip
|
|
160
|
+
cased_events_with would have matched any published Cased event.
|
|
161
|
+
|
|
162
|
+
cased_events_with was called with #{expected_event.inspect} but resulted into #{expanded_expected_event} after it was expanded.
|
|
163
|
+
|
|
164
|
+
This typically happens when an object that includes Cased::Model does not implement either the #cased_id or #to_s method.
|
|
165
|
+
MSG
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# We need to normalize input as it could be a mix of strings and symbols.
|
|
169
|
+
expected_event.deep_symbolize_keys!
|
|
170
|
+
expanded_expected_event = expanded_expected_event.to_a
|
|
171
|
+
|
|
172
|
+
events = cased_events.dup.collect(&:deep_symbolize_keys).collect(&:to_a)
|
|
173
|
+
matching_events = events.select do |event|
|
|
174
|
+
diff = expanded_expected_event - event
|
|
175
|
+
diff.empty?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
matching_events.collect(&:to_h)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# The test published used for the duration of the test.
|
|
182
|
+
#
|
|
183
|
+
# @return [Cased::Publishers::TestPublisher]
|
|
184
|
+
def cased_test_publisher
|
|
185
|
+
@cased_test_publisher ||= Cased::Publishers::TestPublisher.new
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|