mercy 1.3.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 +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +88 -0
- data/LICENSE +22 -0
- data/README.md +100 -0
- data/Rakefile +7 -0
- data/circle.yml +16 -0
- data/lib/bidu.rb +3 -0
- data/lib/bidu/mercy.rb +18 -0
- data/lib/bidu/mercy/class_methods.rb +19 -0
- data/lib/bidu/mercy/concern.rb +10 -0
- data/lib/bidu/mercy/report.rb +44 -0
- data/lib/bidu/mercy/report/active_record.rb +28 -0
- data/lib/bidu/mercy/report/error.rb +62 -0
- data/lib/bidu/mercy/report/multiple.rb +29 -0
- data/lib/bidu/mercy/report/range.rb +54 -0
- data/lib/bidu/mercy/report_builder.rb +24 -0
- data/lib/bidu/mercy/report_config.rb +37 -0
- data/lib/bidu/mercy/status.rb +27 -0
- data/lib/bidu/mercy/status_builder.rb +35 -0
- data/lib/bidu/mercy/version.rb +5 -0
- data/lib/bidu/period_parser.rb +32 -0
- data/lib/json_parser/type_cast_ext.rb +7 -0
- data/mercy.gemspec +33 -0
- data/spec/lib/bidu/mercy/report/error_spec.rb +385 -0
- data/spec/lib/bidu/mercy/report/multiple_spec.rb +122 -0
- data/spec/lib/bidu/mercy/report/range_spec.rb +302 -0
- data/spec/lib/bidu/mercy/report/report_config_spec.rb +39 -0
- data/spec/lib/bidu/mercy/report_builder_spec.rb +72 -0
- data/spec/lib/bidu/mercy/report_spec.rb +44 -0
- data/spec/lib/bidu/mercy/status_builder_spec.rb +84 -0
- data/spec/lib/bidu/mercy/status_spec.rb +135 -0
- data/spec/lib/bidu/period_parser_spec.rb +27 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/fixture_helpers.rb +19 -0
- data/spec/support/models/document.rb +6 -0
- data/spec/support/report/dummy.rb +17 -0
- data/spec/support/schema.rb +11 -0
- metadata +236 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bidu::Mercy::Report::Multiple do
|
|
4
|
+
class Bidu::Mercy::Report::DocTypeError < Bidu::Mercy::Report::Error
|
|
5
|
+
ALLOWED_PARAMETERS=[:period, :threshold]
|
|
6
|
+
DEFAULT_OPTION = {
|
|
7
|
+
threshold: 0.25,
|
|
8
|
+
clazz: Document,
|
|
9
|
+
external_key: :external_id
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
json_parse :doc_type, case: :snake
|
|
13
|
+
|
|
14
|
+
def base
|
|
15
|
+
super.where(doc_type: doc_type)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Bidu::Mercy::Report::Multiple::Dummy < Bidu::Mercy::Report
|
|
20
|
+
include Bidu::Mercy::Report::Multiple
|
|
21
|
+
DEFAULT_OPTION = {
|
|
22
|
+
doc_type: [:a, :b]
|
|
23
|
+
}
|
|
24
|
+
json_parse :doc_type, case: :snake
|
|
25
|
+
|
|
26
|
+
def reports_ids
|
|
27
|
+
[ doc_type ].flatten
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def sub_report_class
|
|
31
|
+
Bidu::Mercy::Report::DocTypeError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key
|
|
35
|
+
:doc_type
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
let(:subject) { described_class::Dummy.new }
|
|
40
|
+
let(:a_errors) { 1 }
|
|
41
|
+
let(:a_successes) { 1 }
|
|
42
|
+
let(:b_errors) { 1 }
|
|
43
|
+
let(:b_successes) { 1 }
|
|
44
|
+
let(:setup) do
|
|
45
|
+
{
|
|
46
|
+
success: { a: a_successes, b: b_successes },
|
|
47
|
+
error: { a: a_errors, b: b_errors }
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
before do
|
|
52
|
+
Document.delete_all
|
|
53
|
+
setup.each do |status, map|
|
|
54
|
+
map.each do |doc_type, quantity|
|
|
55
|
+
quantity.times do
|
|
56
|
+
Document.create(status: status, doc_type: doc_type, external_id: Document.count)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '#error?' do
|
|
63
|
+
context 'when all subreports are with error' do
|
|
64
|
+
it { expect(subject.error?).to be_truthy }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'when one of the reports is not an error' do
|
|
68
|
+
let(:a_successes) { 4 }
|
|
69
|
+
|
|
70
|
+
it { expect(subject.error?).to be_truthy }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'when none of the reports is an error' do
|
|
74
|
+
let(:a_successes) { 4 }
|
|
75
|
+
let(:b_successes) { 4 }
|
|
76
|
+
|
|
77
|
+
it { expect(subject.error?).to be_falsey }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#as_json' do
|
|
82
|
+
let(:expected) do
|
|
83
|
+
{
|
|
84
|
+
'a' => { ids: [2], percentage: 0.5, status: :error },
|
|
85
|
+
'b' => { ids: [3], percentage: 0.5, status: :error },
|
|
86
|
+
status: :error
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context 'when all subreports are with error' do
|
|
91
|
+
it { expect(subject.as_json).to eq(expected) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'when one of the reports is not an error' do
|
|
95
|
+
let(:a_successes) { 4 }
|
|
96
|
+
let(:expected) do
|
|
97
|
+
{
|
|
98
|
+
'a' => { ids: [5], percentage: 0.2, status: :ok },
|
|
99
|
+
'b' => { ids: [6], percentage: 0.5, status: :error },
|
|
100
|
+
status: :error
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it { expect(subject.as_json).to eq(expected) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context 'when none of the reports is an error' do
|
|
108
|
+
let(:a_successes) { 4 }
|
|
109
|
+
let(:b_successes) { 4 }
|
|
110
|
+
let(:expected) do
|
|
111
|
+
{
|
|
112
|
+
'a' => { ids: [8], percentage: 0.2, status: :ok },
|
|
113
|
+
'b' => { ids: [9], percentage: 0.2, status: :ok },
|
|
114
|
+
status: :ok
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it { expect(subject.as_json).to eq(expected) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bidu::Mercy::Report::Range do
|
|
4
|
+
let(:errors) { 1 }
|
|
5
|
+
let(:successes) { 1 }
|
|
6
|
+
let(:old_errors) { 2 }
|
|
7
|
+
let(:old_sucesses) { 2 }
|
|
8
|
+
let(:period) { 1.day }
|
|
9
|
+
let(:scope) { :with_error }
|
|
10
|
+
let(:maximum) { nil }
|
|
11
|
+
let(:minimum) { nil }
|
|
12
|
+
let(:options) do
|
|
13
|
+
{
|
|
14
|
+
period: period,
|
|
15
|
+
scope: scope,
|
|
16
|
+
clazz: Document,
|
|
17
|
+
minimum: minimum,
|
|
18
|
+
maximum: maximum
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
let(:subject) { described_class.new(options) }
|
|
22
|
+
let(:types) { [:a] }
|
|
23
|
+
before do
|
|
24
|
+
Document.all.each(&:destroy)
|
|
25
|
+
types.each do |type|
|
|
26
|
+
successes.times { Document.create status: :success, doc_type: type }
|
|
27
|
+
errors.times do |i|
|
|
28
|
+
Document.create status: :error, external_id: 10 * successes + i, outter_external_id: i, doc_type: type
|
|
29
|
+
end
|
|
30
|
+
old_errors.times do
|
|
31
|
+
Document.create status: :error, created_at: 2.days.ago, updated_at: 2.days.ago, doc_type: type
|
|
32
|
+
end
|
|
33
|
+
old_sucesses.times do
|
|
34
|
+
Document.create status: :success, created_at: 2.days.ago, updated_at: 2.days.ago, doc_type: type
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#status' do
|
|
40
|
+
context 'when looking for maximum counts' do
|
|
41
|
+
context 'when there are more errors than the allowed by the maximum' do
|
|
42
|
+
let(:errors) { 2 }
|
|
43
|
+
let(:maximum) { 1 }
|
|
44
|
+
it { expect(subject.status).to eq(:error) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'when the maximum is 0 and there are no errors' do
|
|
48
|
+
let(:errors) { 0 }
|
|
49
|
+
let(:maximum) { 0 }
|
|
50
|
+
it { expect(subject.status).to eq(:ok) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when the maximum is nil and there are no errors' do
|
|
54
|
+
let(:errors) { 0 }
|
|
55
|
+
let(:maximum) { nil }
|
|
56
|
+
it { expect(subject.status).to eq(:ok) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'when the count is the same as the maximum' do
|
|
60
|
+
let(:errors) { 1 }
|
|
61
|
+
let(:maximum) { 1 }
|
|
62
|
+
it { expect(subject.status).to eq(:ok) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'when the count is less than the maximum' do
|
|
66
|
+
let(:errors) { 1 }
|
|
67
|
+
let(:maximum) { 2 }
|
|
68
|
+
it { expect(subject.status).to eq(:ok) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'when there are older errors out of the period' do
|
|
72
|
+
let(:maximum) { 1 }
|
|
73
|
+
|
|
74
|
+
it 'ignores the older errros' do
|
|
75
|
+
expect(subject.status).to eq(:ok)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'when passing a bigger period' do
|
|
79
|
+
let(:period) { 3.days }
|
|
80
|
+
|
|
81
|
+
it 'consider the older errros' do
|
|
82
|
+
expect(subject.status).to eq(:error)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context 'when looking for minimum' do
|
|
89
|
+
let(:scope) { :with_success }
|
|
90
|
+
context 'when there are less successes than the allowed by the minimum' do
|
|
91
|
+
let(:successes) { 1 }
|
|
92
|
+
let(:minimum) { 2 }
|
|
93
|
+
it { expect(subject.status).to eq(:error) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context 'when the minimum is 0 and there are no sucesses' do
|
|
97
|
+
let(:successes) { 0 }
|
|
98
|
+
let(:minimum) { 0 }
|
|
99
|
+
it { expect(subject.status).to eq(:ok) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
context 'when the minimum is nil and there are no sucesses' do
|
|
103
|
+
let(:successes) { 0 }
|
|
104
|
+
let(:minimum) { nil }
|
|
105
|
+
it { expect(subject.status).to eq(:ok) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'when the count is the same as the minimum' do
|
|
109
|
+
let(:successes) { 1 }
|
|
110
|
+
let(:minimum) { 1 }
|
|
111
|
+
it { expect(subject.status).to eq(:ok) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context 'when the count is greater than the minimum' do
|
|
115
|
+
let(:successes) { 2 }
|
|
116
|
+
let(:minimum) { 1 }
|
|
117
|
+
it { expect(subject.status).to eq(:ok) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context 'when there are older sucesses out of the period' do
|
|
121
|
+
let(:successes) { 0 }
|
|
122
|
+
let(:minimum) { 1 }
|
|
123
|
+
|
|
124
|
+
it 'ignores the older sucesses' do
|
|
125
|
+
expect(subject.status).to eq(:error)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context 'when passing a bigger period' do
|
|
129
|
+
let(:period) { 3.days }
|
|
130
|
+
|
|
131
|
+
it 'consider the older sucesses' do
|
|
132
|
+
expect(subject.status).to eq(:ok)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context 'when looking for a range' do
|
|
139
|
+
let(:scope) { :all }
|
|
140
|
+
let(:minimum) { 2 }
|
|
141
|
+
let(:maximum) { 4 }
|
|
142
|
+
|
|
143
|
+
context 'when there are less documents than the allowed by the minimum' do
|
|
144
|
+
let(:successes) { 1 }
|
|
145
|
+
let(:errors) { 0 }
|
|
146
|
+
it { expect(subject.status).to eq(:error) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'when the count is the same as the minimum' do
|
|
150
|
+
let(:successes) { 1 }
|
|
151
|
+
let(:errors) { 2 }
|
|
152
|
+
it { expect(subject.status).to eq(:ok) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
context 'when the count is inside the range' do
|
|
156
|
+
let(:successes) { 1 }
|
|
157
|
+
let(:errors) { 2 }
|
|
158
|
+
it { expect(subject.status).to eq(:ok) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
context 'when the count is the same as the maximum' do
|
|
162
|
+
let(:successes) { 2 }
|
|
163
|
+
let(:errors) { 2 }
|
|
164
|
+
it { expect(subject.status).to eq(:ok) }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
context 'when the count is greater than the maximum' do
|
|
168
|
+
let(:successes) { 3 }
|
|
169
|
+
let(:errors) { 2 }
|
|
170
|
+
it { expect(subject.status).to eq(:error) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'when the minimum is 0 and the count is 0' do
|
|
174
|
+
let(:successes) { 0 }
|
|
175
|
+
let(:errors) { 0 }
|
|
176
|
+
let(:minimum) { 0 }
|
|
177
|
+
it { expect(subject.status).to eq(:ok) }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context 'when there are older sucesses out of the period' do
|
|
181
|
+
let(:old_errors) { 1 }
|
|
182
|
+
let(:old_sucesses) { 2 }
|
|
183
|
+
|
|
184
|
+
context 'and the regular documents are not enough' do
|
|
185
|
+
let(:successes) { 1 }
|
|
186
|
+
let(:errors) { 0 }
|
|
187
|
+
|
|
188
|
+
it 'ignores the older sucesses' do
|
|
189
|
+
expect(subject.status).to eq(:error)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context 'when passing a bigger period' do
|
|
193
|
+
let(:period) { 3.days }
|
|
194
|
+
|
|
195
|
+
it 'consider the older sucesses' do
|
|
196
|
+
expect(subject.status).to eq(:ok)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
context 'and the regular documents are almost in the limit' do
|
|
202
|
+
let(:successes) { 2 }
|
|
203
|
+
let(:errors) { 1 }
|
|
204
|
+
|
|
205
|
+
it 'ignores the older sucesses' do
|
|
206
|
+
expect(subject.status).to eq(:ok)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context 'when passing a bigger period' do
|
|
210
|
+
let(:period) { 3.days }
|
|
211
|
+
|
|
212
|
+
it 'consider the older documents' do
|
|
213
|
+
expect(subject.status).to eq(:error)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
describe '#count' do
|
|
222
|
+
let(:types) { [:a, :b] }
|
|
223
|
+
let(:errors) { 1 }
|
|
224
|
+
let(:scope) { :with_error }
|
|
225
|
+
|
|
226
|
+
it 'returns all the documents found' do
|
|
227
|
+
expect(subject.count).to eq(2)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context 'when configuring with a complex scope' do
|
|
231
|
+
let(:old_errors) { 0 }
|
|
232
|
+
let(:scope) { :'with_error.type_b' }
|
|
233
|
+
let(:errors) { 1 }
|
|
234
|
+
|
|
235
|
+
context 'as symbol' do
|
|
236
|
+
let(:scope) { :'with_error.type_b' }
|
|
237
|
+
|
|
238
|
+
it 'fetches from each scope in order' do
|
|
239
|
+
expect(subject.count).to eq(1)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
context 'as string where scope' do
|
|
244
|
+
let(:scope) { "status = 'error' and doc_type = 'b'" }
|
|
245
|
+
|
|
246
|
+
it 'fetches from each scope in order' do
|
|
247
|
+
expect(subject.count).to eq(1)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
context 'as hash where scope' do
|
|
252
|
+
let(:scope) { { status: :error, doc_type: :b } }
|
|
253
|
+
|
|
254
|
+
it 'fetches from each scope in order' do
|
|
255
|
+
expect(subject.count).to eq(1)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
describe '#error?' do
|
|
262
|
+
let(:errors) { 2 }
|
|
263
|
+
let(:maximum) { 1 }
|
|
264
|
+
|
|
265
|
+
context 'when errors count overcome maximum' do
|
|
266
|
+
it { expect(subject.error?).to be_truthy }
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
context 'when errors count do not overcome maximum' do
|
|
270
|
+
let(:errors) { 0 }
|
|
271
|
+
it { expect(subject.error?).to be_falsey }
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
describe '#as_json' do
|
|
276
|
+
let(:expected) do
|
|
277
|
+
{ count: count_expected, status: status_expected }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
context 'when everything is ok' do
|
|
281
|
+
let(:errors) { 1 }
|
|
282
|
+
let(:status_expected) { :ok }
|
|
283
|
+
let(:count_expected) { errors }
|
|
284
|
+
let(:maximum) { 2 }
|
|
285
|
+
|
|
286
|
+
it 'returns the count and status' do
|
|
287
|
+
expect(subject.as_json).to eq(expected)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
context 'when there is an error' do
|
|
292
|
+
let(:errors) { 2 }
|
|
293
|
+
let(:status_expected) { :error }
|
|
294
|
+
let(:count_expected) { errors }
|
|
295
|
+
let(:maximum) { 1 }
|
|
296
|
+
|
|
297
|
+
it 'returns the count and status' do
|
|
298
|
+
expect(subject.as_json).to eq(expected)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Bidu::Mercy::ReportConfig do
|
|
4
|
+
let(:config) { {} }
|
|
5
|
+
let(:parameters) { {} }
|
|
6
|
+
let(:subject) { described_class.new(config) }
|
|
7
|
+
|
|
8
|
+
describe '#build' do
|
|
9
|
+
context 'when no config is given' do
|
|
10
|
+
it do
|
|
11
|
+
expect(subject.build(parameters)).to be_a(Bidu::Mercy::Report::Error)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'when a dummy type is given' do
|
|
16
|
+
let(:config) { { type: :dummy } }
|
|
17
|
+
|
|
18
|
+
it do
|
|
19
|
+
expect(subject.build(parameters)).to be_a(Bidu::Mercy::Report::Dummy)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'when a class is given as type' do
|
|
24
|
+
let(:config) { { type: Bidu::Mercy::Report::Dummy } }
|
|
25
|
+
|
|
26
|
+
it do
|
|
27
|
+
expect(subject.build(parameters)).to be_a(Bidu::Mercy::Report::Dummy)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context 'when a global class is given as type' do
|
|
32
|
+
let(:config) { { type: Dummy } }
|
|
33
|
+
|
|
34
|
+
it do
|
|
35
|
+
expect(subject.build(parameters)).to be_a(Dummy)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|