invoca-utils 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/pipeline.yml +20 -0
- data/.gitignore +1 -3
- data/.rspec +3 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Appraisals +13 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +9 -16
- data/Gemfile.lock +39 -48
- data/Rakefile +9 -6
- data/gemfiles/activesupport_5.gemfile +12 -0
- data/gemfiles/activesupport_5.gemfile.lock +58 -0
- data/gemfiles/activesupport_6.gemfile +12 -0
- data/gemfiles/activesupport_6.gemfile.lock +59 -0
- data/gemfiles/activesupport_7.gemfile +12 -0
- data/gemfiles/activesupport_7.gemfile.lock +57 -0
- data/invoca-utils.gemspec +18 -6
- data/lib/invoca/utils/version.rb +1 -1
- data/{test → spec}/helpers/constant_overrides.rb +0 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/array_spec.rb +20 -0
- data/spec/unit/enumerable_spec.rb +80 -0
- data/{test/unit/exceptions_test.rb → spec/unit/exceptions_spec.rb} +17 -17
- data/spec/unit/guaranteed_utf8_string_spec.rb +260 -0
- data/spec/unit/hash_spec.rb +81 -0
- data/spec/unit/hash_with_indifferent_access_spec.rb +100 -0
- data/spec/unit/map_compact_spec.rb +25 -0
- data/{test/unit/module_test.rb → spec/unit/module_spec.rb} +4 -4
- data/spec/unit/multi_sender_spec.rb +54 -0
- data/{test/unit/stable_sort_test.rb → spec/unit/stable_sort_spec.rb} +14 -14
- data/spec/unit/time_calculations_spec.rb +39 -0
- data/{test/unit/utils_test.rb → spec/unit/utils_spec.rb} +14 -14
- metadata +59 -37
- data/.jenkins/Jenkinsfile +0 -50
- data/.jenkins/ruby_build_pod.yml +0 -19
- data/test/test_helper.rb +0 -14
- data/test/unit/array_test.rb +0 -20
- data/test/unit/enumerable_test.rb +0 -80
- data/test/unit/guaranteed_utf8_string_test.rb +0 -263
- data/test/unit/hash_test.rb +0 -81
- data/test/unit/hash_with_indifferent_access_test.rb +0 -100
- data/test/unit/map_compact_test.rb +0 -25
- data/test/unit/multi_sender_test.rb +0 -56
- data/test/unit/time_calculations_test.rb +0 -39
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
3
|
+
require_relative '../spec_helper'
|
4
4
|
|
5
|
-
|
5
|
+
describe Invoca::Utils do
|
6
6
|
context "exceptions" do
|
7
7
|
context ".retry_on_exception" do
|
8
|
-
|
8
|
+
it "default retries: to 1" do
|
9
9
|
times = 0
|
10
10
|
tries = []
|
11
11
|
result = Invoca::Utils.retry_on_exception(ArgumentError) do |try|
|
12
12
|
tries << try
|
13
13
|
times += 1
|
14
14
|
end
|
15
|
-
|
16
|
-
|
15
|
+
expect(result).to eq(1)
|
16
|
+
expect(tries).to eq([0])
|
17
17
|
end
|
18
18
|
|
19
19
|
context "when never raising an exception" do
|
20
|
-
|
20
|
+
it "return result" do
|
21
21
|
times = 0
|
22
22
|
tries = []
|
23
23
|
result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 2) do |try|
|
@@ -26,26 +26,26 @@ class ExceptionsTest < Minitest::Test
|
|
26
26
|
try == 0 and raise ArgumentError, '!!!'
|
27
27
|
times
|
28
28
|
end
|
29
|
-
|
30
|
-
|
29
|
+
expect(result).to eq(2)
|
30
|
+
expect(tries).to eq([0,1])
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
context "when always raising an exception" do
|
35
|
-
|
35
|
+
it "retry and finally raise" do
|
36
36
|
tries = []
|
37
|
-
|
37
|
+
expect do
|
38
38
|
Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do |try|
|
39
39
|
tries << try
|
40
40
|
raise ArgumentError, "!!! #{try + 1}"
|
41
41
|
end
|
42
|
-
end
|
43
|
-
|
42
|
+
end.to raise_exception(ArgumentError, /!!! 2/)
|
43
|
+
expect(tries).to eq([0,1])
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
context "when raising but then succeeding" do
|
48
|
-
|
48
|
+
it "retry and finally return result" do
|
49
49
|
times = 0
|
50
50
|
result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do
|
51
51
|
times += 1
|
@@ -55,12 +55,12 @@ class ExceptionsTest < Minitest::Test
|
|
55
55
|
times
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
expect(result).to eq(2)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
context "when raising different exceptions (array notation) but then succeeding" do
|
63
|
-
|
63
|
+
it "retry and finally return result" do
|
64
64
|
times = 0
|
65
65
|
tries = []
|
66
66
|
result = Invoca::Utils.retry_on_exception([ArgumentError, RuntimeError], retries: 2) do |try|
|
@@ -75,8 +75,8 @@ class ExceptionsTest < Minitest::Test
|
|
75
75
|
times
|
76
76
|
end
|
77
77
|
end
|
78
|
-
|
79
|
-
|
78
|
+
expect(result).to eq(3)
|
79
|
+
expect(tries).to eq([0, 1, 2])
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/guaranteed_utf8_string'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Invoca::Utils::GuaranteedUTF8String do
|
7
|
+
class HasNoTo_sMethod
|
8
|
+
undef :to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
class BasicObjectWithKernelMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
class ConvertibleToString
|
15
|
+
attr_reader :to_s
|
16
|
+
|
17
|
+
def initialize(string)
|
18
|
+
@to_s = string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context '.normalize_string' do
|
23
|
+
it 'raise an error if called with an object with no to_s method' do
|
24
|
+
expect do
|
25
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(HasNoTo_sMethod.new)
|
26
|
+
end.to raise_exception(ArgumentError, /must be passed a string or an object with a non-Kernel \.to_s method but instead was HasNoTo_sMethod/)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raise an error if called with a basic Ruby object' do
|
30
|
+
expect do
|
31
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(BasicObjectWithKernelMethods.new)
|
32
|
+
end.to raise_exception(ArgumentError,
|
33
|
+
/must be passed a string or an object with a non-Kernel \.to_s method but instead was BasicObjectWithKernelMethods/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'not mutate the original string' do
|
37
|
+
ascii_string = 'new string'.encode('ASCII')
|
38
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(ascii_string)
|
39
|
+
|
40
|
+
expect(encoded_string).to eq(ascii_string)
|
41
|
+
expect(ascii_string.encoding).to eq(Encoding::ASCII)
|
42
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'return UTF-8 encoded string' do
|
46
|
+
original_string = "this,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n"
|
47
|
+
|
48
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string)
|
49
|
+
|
50
|
+
expect(encoded_string).to eq(original_string)
|
51
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
52
|
+
end
|
53
|
+
|
54
|
+
context "normalize_utf16" do
|
55
|
+
UTF16_LE_BOM = "\xFF\xFE"
|
56
|
+
UTF16_BE_BOM = "\xFE\xFF"
|
57
|
+
UTF16_LE_TEST_STRING = (UTF16_LE_BOM + "v\x00a\x00l\x00i\x00d\x00,\x00u\x00t\x00f\x00-\x001\x006\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY').freeze
|
58
|
+
UTF16_BE_TEST_STRING = (UTF16_BE_BOM + "\x00v\x00a\x00l\x00i\x00d\x00,\x00u\x00t\x00f\x00-\x001\x006\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY').freeze
|
59
|
+
|
60
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string when true' do
|
61
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_LE_TEST_STRING, normalize_utf16: true)
|
62
|
+
|
63
|
+
expect(encoded_string).to eq("valid,utf-16\nsecond")
|
64
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'not check for UTF-16LE in BINARY and return UTF-8 encoded string when false' do
|
68
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_LE_TEST_STRING, normalize_utf16: false)
|
69
|
+
expected = "ÿþv\u0000a\u0000l\u0000i\u0000d\u0000,\u0000u\u0000t\u0000f\u0000-\u00001\u00006\u0000\n\u0000s\u0000e\u0000c\u0000o\u0000n\u0000d\u0000"
|
70
|
+
|
71
|
+
expect(encoded_string).to eq(expected)
|
72
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when true' do
|
76
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_BE_TEST_STRING, normalize_utf16: true)
|
77
|
+
|
78
|
+
expect(encoded_string).to eq("valid,utf-16\nsecond")
|
79
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'not check for UTF-16BE in BINARY and return UTF-8 encoded string when false' do
|
83
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_BE_TEST_STRING, normalize_utf16: false)
|
84
|
+
expected = "þÿ\u0000v\u0000a\u0000l\u0000i\u0000d\u0000,\u0000u\u0000t\u0000f\u0000-\u00001\u00006\u0000\n\u0000s\u0000e\u0000c\u0000o\u0000n\u0000d"
|
85
|
+
|
86
|
+
expect(encoded_string).to eq(expected)
|
87
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "containing embedded CP1252" do
|
91
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string with "private" CP1252 when normalize_utf16: true, normalize_cp1252: false' do
|
92
|
+
original_string = (UTF16_LE_BOM + "\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY')
|
93
|
+
|
94
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: false)
|
95
|
+
|
96
|
+
expect(encoded_string).to eq("\u0091smart quotes\u0092\nsecond")
|
97
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string with normalized CP1252 when normalize_utf16: true, normalize_cp1252: true' do
|
101
|
+
original_string = (UTF16_LE_BOM + "\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY')
|
102
|
+
|
103
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true)
|
104
|
+
|
105
|
+
expect(encoded_string).to eq("‘smart quotes’\nsecond")
|
106
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when normalize_utf16: true, normalize_cp1252: false' do
|
110
|
+
original_string = (UTF16_BE_BOM + "\x00\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY')
|
111
|
+
|
112
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: false)
|
113
|
+
|
114
|
+
expect(encoded_string).to eq("\u0091smart quotes\u0092\nsecond")
|
115
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when normalize_utf16: true, normalize_cp1252: true' do
|
119
|
+
original_string = (UTF16_BE_BOM + "\x00\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY')
|
120
|
+
|
121
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: true)
|
122
|
+
|
123
|
+
expect(encoded_string).to eq("‘smart quotes’\nsecond")
|
124
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'normalize_cp1252' do
|
130
|
+
before do
|
131
|
+
@string = "This,is,NOT,a,valid,utf-8,csv,string\r\none,two,three,four,\x81five,\x91smart quotes\x92,\x93suck!\x94\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raise ArgumentError when false' do
|
135
|
+
expect do
|
136
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_cp1252: false)
|
137
|
+
end.to raise_exception(ArgumentError)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'return UTF-8 encoded string after falling back to CP1252 encoding when true' do
|
141
|
+
expected_string = "This,is,NOT,a,valid,utf-8,csv,string\none,two,three,four,~five,‘smart quotes’,“suck!”\n"
|
142
|
+
|
143
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string)
|
144
|
+
|
145
|
+
expect(encoded_string).to eq(expected_string)
|
146
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "encode all 255 UTF-8 characters, returning ~ when the character isn't mapped in CP1252" do
|
150
|
+
all_8_bit_characters = (1..255).map(&:chr).join
|
151
|
+
|
152
|
+
final_utf_8_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(all_8_bit_characters)
|
153
|
+
expected_string = "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000A\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007F€~‚ƒ„…†‡ˆ‰Š‹Œ~Ž~~‘’“”•–—˜™š›œ~žŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
|
154
|
+
|
155
|
+
expect(final_utf_8_string).to eq(expected_string)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'normalize_newlines' do
|
160
|
+
before do
|
161
|
+
@string = "This string\n\n\n has line feeds\ncarriage\r\r returns\rand Windows\r\n\r\n new line chars\r\nend of \n\r\r\r\nstring"
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'return UTF-8 encoded string without normalized return chars when false' do
|
165
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_newlines: false)
|
166
|
+
|
167
|
+
expect(encoded_string).to eq(@string)
|
168
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'return UTF-8 encoded string with normalized return chars when true' do
|
172
|
+
expected_string = "This string\n\n\n has line feeds\ncarriage\n\n returns\nand Windows\n\n new line chars\nend of \n\n\n\nstring"
|
173
|
+
|
174
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_newlines: true)
|
175
|
+
|
176
|
+
expect(encoded_string).to eq(expected_string)
|
177
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'remove_utf8_bom' do
|
182
|
+
before do
|
183
|
+
@original_string = "\xEF\xBB\xBFthis,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n"
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'return UTF-8 encoded string with BOM intact when false' do
|
187
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@original_string, remove_utf8_bom: false)
|
188
|
+
|
189
|
+
expect(encoded_string).to eq("\xEF\xBB\xBFthis,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n")
|
190
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'return UTF-8 encoded string without BOM when true' do
|
194
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@original_string, remove_utf8_bom: true)
|
195
|
+
|
196
|
+
expect(encoded_string).to eq("this,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n")
|
197
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'replace_unicode_beyond_ffff' do
|
202
|
+
before do
|
203
|
+
@string = "This string has some ✓ valid UTF-8 but also some 😹 emoji \xf0\x9f\x98\xb9 that are > U+FFFF"
|
204
|
+
end
|
205
|
+
|
206
|
+
it "consider UTF-8 code points that take > 3 bytes (above U+FFFF) to be invalid (since MySQL can't store them unless column is declared mb4) and encode them as ~ when false" do
|
207
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, replace_unicode_beyond_ffff: false)
|
208
|
+
|
209
|
+
expect(encoded_string).to eq(@string)
|
210
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "consider UTF-8 code points that take > 3 bytes (above U+FFFF) to be invalid (since MySQL can't store them unless column is declared mb4) and encode them as ~ when true" do
|
214
|
+
expected_string = 'This string has some ✓ valid UTF-8 but also some ~ emoji ~ that are > U+FFFF'
|
215
|
+
|
216
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, replace_unicode_beyond_ffff: true)
|
217
|
+
|
218
|
+
expect(encoded_string).to eq(expected_string)
|
219
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context ".normalize_strings" do
|
224
|
+
it "walk json doc, replacing strings in: values, inside array elements, and hash keys and values" do
|
225
|
+
json_doc = {
|
226
|
+
'😹' => "\xE2\x9C\x93 laughing cat",
|
227
|
+
['😹'] => ["\xE2", "\xf0\x9f\x98\xb9", { "newline" => "\r\n" }],
|
228
|
+
'cp1252' => "\x91smart quotes\x92"
|
229
|
+
}
|
230
|
+
|
231
|
+
normalized_json = Invoca::Utils::GuaranteedUTF8String.normalize_all_strings(json_doc,
|
232
|
+
normalize_utf16: true,
|
233
|
+
normalize_cp1252: true,
|
234
|
+
normalize_newlines: true,
|
235
|
+
remove_utf8_bom: true,
|
236
|
+
replace_unicode_beyond_ffff: true)
|
237
|
+
|
238
|
+
expect({
|
239
|
+
'~' => "✓ laughing cat",
|
240
|
+
['~'] => ["â", "~", { "newline" => "\n" }],
|
241
|
+
'cp1252' => "‘smart quotes’"
|
242
|
+
}).to eq(normalized_json)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'constructor' do
|
248
|
+
it 'call normalize_string with the default conversions' do
|
249
|
+
expect(Invoca::Utils::GuaranteedUTF8String).to receive(:normalize_string).with('')
|
250
|
+
|
251
|
+
Invoca::Utils::GuaranteedUTF8String.new('').to_string
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'do the same when using to_s alias' do
|
255
|
+
expect(Invoca::Utils::GuaranteedUTF8String).to receive(:normalize_string).with('')
|
256
|
+
|
257
|
+
Invoca::Utils::GuaranteedUTF8String.new('').to_s
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/hash.rb'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Hash do
|
7
|
+
|
8
|
+
context 'select_hash' do
|
9
|
+
it 'return a hash containing key/values identified by the block' do
|
10
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.select_hash { |key, value| key < value }).to eq({ 1 => 2, 3 => 4 })
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'handle blocks that only check values' do
|
14
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.select_hash { |value| value != 2 }).to eq({ 3 => 4, 6 => 5 })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'map_hash' do
|
19
|
+
it 'return a hash containing values updated by the block' do
|
20
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.map_hash { |key, value| key < value }).to eq({ 1 => true, 3 => true, 6 => false })
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handle blocks that only receive values' do
|
24
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.map_hash { |value| value * 2 }).to eq({ 1 => 4, 3 => 8, 6 => 10 })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'partition_hash' do
|
29
|
+
it 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
|
30
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([1, 3])).to eq([{ 1 => 2, 3 => 4 }, { 6 => 5 }])
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
|
34
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |key, value| key < value }).to eq([{ 1 => 2, 3 => 4 }, { 6 => 5 }])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'handle no matches' do
|
38
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([100])).to eq([{}, { 1 => 2, 3 => 4, 6 => 5 }])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'handle all matches' do
|
42
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |_key, _value| true }).to eq([{ 1 => 2, 3 => 4, 6 => 5 }, {}])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '- operator' do
|
47
|
+
it 'return a hash with pairs removed that match the keys in rhs array' do
|
48
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [1, 6]).to eq({ 3 => 4 })
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'handle empty rhs array' do
|
52
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - []).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'handle no matches in rhs array' do
|
56
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [100, 600]).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'handle all matches in rhs array' do
|
60
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [1, 3, 6]).to eq({})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context '& operator' do
|
65
|
+
it 'return a hash with pairs removed that do NOT match the keys in rhs array' do
|
66
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [1, 6]).to eq({ 1 => 2, 6 => 5 })
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'handle empty rhs array' do
|
70
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & []).to eq({})
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'handle no matches in rhs array' do
|
74
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [100, 600]).to eq({})
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'handle all matches in rhs array' do
|
78
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [1, 3, 6]).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require_relative '../../lib/invoca/utils/hash_with_indifferent_access.rb'
|
5
|
+
require_relative '../spec_helper'
|
6
|
+
|
7
|
+
describe HashWithIndifferentAccess do
|
8
|
+
|
9
|
+
context 'partition_hash' do
|
10
|
+
before do
|
11
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
|
15
|
+
expect(@hash_to_test.partition_hash(['one', 'three'])).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
|
19
|
+
expect(@hash_to_test.partition_hash { |key, _value| ['one', 'three'].include?(key) }).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'handle no matches' do
|
23
|
+
expect(@hash_to_test.partition_hash([:not_found])).to eq([{}, @hash_to_test])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'handle all matches' do
|
27
|
+
expect(@hash_to_test.partition_hash { |_key, _value| true }).to eq([@hash_to_test, {}])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'handle symbols for key matching' do
|
31
|
+
expect(@hash_to_test.partition_hash([:one, :three])).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'return HashWithIndifferentAccess objects' do
|
35
|
+
matched, unmatched = @hash_to_test.partition_hash([:one, :three])
|
36
|
+
expect(matched.is_a?(HashWithIndifferentAccess)).to be_truthy
|
37
|
+
expect(unmatched.is_a?(HashWithIndifferentAccess)).to be_truthy
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context '- operator' do
|
42
|
+
before do
|
43
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'return a hash with pairs removed that match the keys in rhs array' do
|
47
|
+
expect(@hash_to_test - ['one', 'six']).to eq({ 'three' => 4 })
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'handle empty rhs array' do
|
51
|
+
expect(@hash_to_test - []).to eq(@hash_to_test)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'handle no matches in rhs array' do
|
55
|
+
expect(@hash_to_test - ['100', '600']).to eq(@hash_to_test)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'handle all matches in rhs array' do
|
59
|
+
expect(@hash_to_test - ['one', 'three', 'six']).to eq({})
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'handle symbols for key matching' do
|
63
|
+
expect(@hash_to_test - [:one, :three]).to eq({ 'six' => 5 })
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'return HashWithIndifferentAccess object' do
|
67
|
+
expect((@hash_to_test - [:one, :three]).is_a?(HashWithIndifferentAccess)).to be_truthy
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '& operator' do
|
72
|
+
before do
|
73
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'return a hash with pairs removed that do NOT match the keys in rhs array' do
|
77
|
+
expect(@hash_to_test & ['one', 'six']).to eq({ 'one' => 2, 'six' => 5 })
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'handle empty rhs array' do
|
81
|
+
expect(@hash_to_test & []).to eq({})
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'handle no matches in rhs array' do
|
85
|
+
expect(@hash_to_test & ['100', '600']).to eq({})
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'handle all matches in rhs array' do
|
89
|
+
expect(@hash_to_test & ['one', 'three', 'six']).to eq(@hash_to_test)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'handle symbols for key matching' do
|
93
|
+
expect(@hash_to_test & [:one, :three]).to eq({ 'one' => 2, 'three' => 4 })
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'return HashWithIndifferentAccess object' do
|
97
|
+
expect((@hash_to_test & [:one, :three]).is_a?(HashWithIndifferentAccess)).to be_truthy
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
describe Enumerable do
|
6
|
+
it "map_compact" do
|
7
|
+
expect([1, 2, nil, 3, 4].map_compact { |item| item**2 if (nil == item ? nil : item.odd?) }).to eq([1, 9])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "map_compact to empty if nothing matches" do
|
11
|
+
expect({:a => 'aaa', :b => 'bbb'}.map_compact { |key, value| value if key == :c }).to eq([])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "map_compact a hash" do
|
15
|
+
expect({:a => 'aaa', :b => 'bbb'}.map_compact { |key, value| value if key == :b }).to eq(['bbb'])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "map_compact empty collection" do
|
19
|
+
expect([].map_compact { |item| true }).to eq([])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "not map_compact false" do
|
23
|
+
expect([nil, false].map_compact { |a| a }).to eq([false])
|
24
|
+
end
|
25
|
+
end
|
@@ -3,9 +3,9 @@
|
|
3
3
|
# must require active_support's alias_method_chain first, to ensure that our module monkey patches it
|
4
4
|
require 'active_support/core_ext/module/aliasing'
|
5
5
|
require_relative '../../lib/invoca/utils/module.rb'
|
6
|
-
require_relative '../
|
6
|
+
require_relative '../spec_helper'
|
7
7
|
|
8
|
-
|
8
|
+
describe Module do
|
9
9
|
class NumberFun
|
10
10
|
def self.around_filter(around_method, method_names)
|
11
11
|
method_names.each do |meth|
|
@@ -32,8 +32,8 @@ class ModuleTest < Minitest::Test
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'alias_method_chain' do
|
35
|
-
|
36
|
-
|
35
|
+
it 'not cause infinite recursion when double aliasing the same method' do
|
36
|
+
expect(NumberFun.new.number_printer(3)).to eq(4)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/multi_sender.rb'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Invoca::Utils::MultiSender do
|
7
|
+
# create enumerable class for testing
|
8
|
+
class LinkedList
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize(head, tail = nil)
|
12
|
+
@head, @tail = head, tail
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(item)
|
16
|
+
LinkedList.new(item, self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
[@head, @tail].inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
if block_given?
|
25
|
+
block.call(@head)
|
26
|
+
@tail&.each(&block)
|
27
|
+
else
|
28
|
+
to_enum(:each)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with custom Enumerable' do
|
34
|
+
before do
|
35
|
+
linked_list = LinkedList.new('some') << 'short' << 'words'
|
36
|
+
@multi_sender = Invoca::Utils::MultiSender.new(linked_list, :map)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'call the same method on each item in an Enumerable and return the results as an array' do
|
40
|
+
expect(@multi_sender.length).to eq([5, 5, 4])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'handle methods with arguments' do
|
44
|
+
expect(@multi_sender.slice(1, 2)).to eq(['or', 'ho', 'om'])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with built-in Array' do
|
49
|
+
it 'call the same method on each item in an Array and return the results as an array' do
|
50
|
+
multi_sender = Invoca::Utils::MultiSender.new(['some', 'short', 'words'], :map)
|
51
|
+
expect(multi_sender.length).to eq([4, 5, 5])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|