bidu-house 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bf8431dbc896d4009687b3896a2d11f268d48a18
4
+ data.tar.gz: 295a7b922f4af26fb58e8a5b4d59ce6cba59c512
5
+ SHA512:
6
+ metadata.gz: 94962c145b428b9a261b5df1871586a556d15553ebdd97ef1c36fa2f1ba3e171e3e21672f60df3a624329c05dc72375dac38dd4f042c6840dc95710def42ec16
7
+ data.tar.gz: f596699a632c86fd136b50312db1490528b6d9bdfadde67405a9a19add49122fb9130d0009c3cf2d9c347bb4af044577baba07fe96075e15d1d199c3e7cdc6b8
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ coverage
2
+ pkg
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bidu-house (0.1.0)
5
+ activesupport
6
+ bidu-core_ext
7
+ concern_builder
8
+ json_parser (~> 1.1)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activemodel (4.2.3)
14
+ activesupport (= 4.2.3)
15
+ builder (~> 3.1)
16
+ activerecord (4.2.3)
17
+ activemodel (= 4.2.3)
18
+ activesupport (= 4.2.3)
19
+ arel (~> 6.0)
20
+ activesupport (4.2.3)
21
+ i18n (~> 0.7)
22
+ json (~> 1.7, >= 1.7.7)
23
+ minitest (~> 5.1)
24
+ thread_safe (~> 0.3, >= 0.3.4)
25
+ tzinfo (~> 1.1)
26
+ arel (6.0.0)
27
+ bidu-core_ext (1.0.0)
28
+ activesupport
29
+ builder (3.2.2)
30
+ concern_builder (0.0.1)
31
+ activesupport
32
+ diff-lcs (1.2.5)
33
+ docile (1.1.5)
34
+ i18n (0.7.0)
35
+ json (1.8.3)
36
+ json_parser (1.1.0)
37
+ activesupport
38
+ concern_builder
39
+ minitest (5.7.0)
40
+ rake (10.4.2)
41
+ rspec (2.99.0)
42
+ rspec-core (~> 2.99.0)
43
+ rspec-expectations (~> 2.99.0)
44
+ rspec-mocks (~> 2.99.0)
45
+ rspec-core (2.99.2)
46
+ rspec-expectations (2.99.2)
47
+ diff-lcs (>= 1.1.3, < 2.0)
48
+ rspec-mocks (2.99.4)
49
+ simplecov (0.10.0)
50
+ docile (~> 1.1.0)
51
+ json (~> 1.8)
52
+ simplecov-html (~> 0.10.0)
53
+ simplecov-html (0.10.0)
54
+ sqlite3 (1.3.10)
55
+ thread_safe (0.3.5)
56
+ tzinfo (1.2.2)
57
+ thread_safe (~> 0.1)
58
+
59
+ PLATFORMS
60
+ ruby
61
+
62
+ DEPENDENCIES
63
+ activerecord
64
+ bidu-house!
65
+ bundler (~> 1.6)
66
+ rake
67
+ rspec (~> 2.14)
68
+ simplecov
69
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Bidu Corretora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # house
2
+ Health Check Gem
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task test: :spec
data/house.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bidu/house/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'bidu-house'
8
+ gem.version = Bidu::House::VERSION
9
+ gem.authors = ["Bidu Dev's Team"]
10
+ gem.email = ["dev@bidu.com.br"]
11
+ gem.homepage = 'https://github.com/Bidu/house'
12
+ gem.description = 'Gem for easy health check'
13
+ gem.summary = gem.description
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|gem|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'activesupport'
21
+ gem.add_runtime_dependency 'concern_builder'
22
+ gem.add_runtime_dependency 'bidu-core_ext'
23
+ gem.add_runtime_dependency 'json_parser', '~> 1.1'
24
+
25
+ gem.add_development_dependency "activerecord"
26
+ gem.add_development_dependency "sqlite3"
27
+
28
+ gem.add_development_dependency "bundler", "~> 1.6"
29
+ gem.add_development_dependency "rake"
30
+ gem.add_development_dependency "rspec", "~> 2.14"
31
+ gem.add_development_dependency 'simplecov'
32
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_record'
2
+
3
+ module ActiveRecord
4
+ class Relation
5
+ def percentage(*filters)
6
+ return 0 if count == 0
7
+
8
+ if filters.first.is_a?(Symbol)
9
+ filtered = filters.inject(self) do |relation, scope|
10
+ relation.public_send(scope)
11
+ end
12
+ else
13
+ filtered = where(*filters)
14
+ end
15
+
16
+ filtered.count * 1.0 / count
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Bidu
2
+ module House
3
+ module ClassMethods
4
+
5
+ def status_report(*attr_names)
6
+ id = attr_names.first
7
+ options = {
8
+ id: id
9
+ }.merge(attr_names.extract_options!)
10
+
11
+ self.status_builder.add_report_config(id, options)
12
+ end
13
+
14
+ def status_builder
15
+ @status_builder ||= Bidu::House::StatusBuilder.new
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Bidu
2
+ module House
3
+ extend ActiveSupport::Concern
4
+
5
+ def render_status(key)
6
+ status = self.class.status_builder.build(key, params)
7
+ render json: status.as_json, status: status.status
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ module Bidu
2
+ module House
3
+ class ErrorReport
4
+ include JsonParser
5
+
6
+ attr_reader :json
7
+
8
+ json_parse :threshold, type: :float
9
+ json_parse :period, type: :period
10
+ json_parse :scope, :id, :clazz, :external_key, case: :snake
11
+
12
+ def initialize(options)
13
+ @json = {
14
+ external_key: :id,
15
+ threshold: 0.02,
16
+ period: 1.day,
17
+ scope: :with_error
18
+ }.merge(options)
19
+ end
20
+
21
+ def status
22
+ @status ||= error? ? :error : :ok
23
+ end
24
+
25
+ def percentage
26
+ @percentage ||= last_entires.percentage(scope)
27
+ end
28
+
29
+ def scoped
30
+ @scoped ||= last_entires.public_send(scope)
31
+ end
32
+
33
+ def error?
34
+ percentage > threshold
35
+ end
36
+
37
+ def as_json
38
+ {
39
+ ids: scoped.map(&external_key),
40
+ percentage: percentage
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def last_entires
47
+ @last_entires ||= clazz.where('updated_at >= ?', period.seconds.ago)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ module Bidu
2
+ module House
3
+ class ReportBuilder
4
+ def build(key, parameters = {})
5
+ params = parameters.slice(:period, :threshold)
6
+ config = configs[key].merge(params)
7
+ Bidu::House::ErrorReport.new(config)
8
+ end
9
+
10
+ def add_config(key, config)
11
+ configs[key] = config
12
+ end
13
+
14
+ private
15
+
16
+ def configs
17
+ @configs ||= {}
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Bidu
2
+ module House
3
+ class Status
4
+ attr_reader :reports
5
+
6
+ def initialize(reports)
7
+ @reports = reports
8
+ end
9
+
10
+ def status
11
+ reports.any? { |r| r.error? } ? :error : :ok
12
+ end
13
+
14
+ def as_json
15
+ {
16
+ status: status
17
+ }.merge(reports_jsons)
18
+ end
19
+
20
+ private
21
+
22
+ def reports_jsons
23
+ reports.map(&:as_json).as_hash(reports.map(&:id))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ module Bidu
2
+ module House
3
+ class StatusBuilder
4
+ def build(key, parameters = {})
5
+ Bidu::House::Status.new(reports_for(key, parameters))
6
+ end
7
+
8
+ def add_report_config(key, config)
9
+ status_key = config.delete(:on) || :default
10
+ report_builder.add_config(key, config)
11
+ config_for(status_key) << key
12
+ end
13
+
14
+ private
15
+
16
+ def report_builder
17
+ @report_builder ||= Bidu::House::ReportBuilder.new
18
+ end
19
+
20
+ def reports_for(key, parameters)
21
+ config_for(key).map do |report_key|
22
+ report_builder.build(report_key, parameters)
23
+ end
24
+ end
25
+
26
+ def configs
27
+ @configs ||= {}
28
+ end
29
+
30
+ def config_for(key)
31
+ configs[key] ||= []
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Bidu
2
+ module House
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
data/lib/bidu/house.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'active_record/relation_ext'
2
+ require 'concern_builder'
3
+ require 'bidu/core_ext'
4
+ require 'bidu/period_parser'
5
+ require 'json_parser'
6
+ require 'json_parser/type_cast_ext'
7
+
8
+ module Bidu
9
+ module House
10
+ require 'bidu/house/concern'
11
+ require 'bidu/house/error_report'
12
+ require 'bidu/house/status'
13
+ require 'bidu/house/report_builder'
14
+ require 'bidu/house/status_builder'
15
+ require 'bidu/house/class_methods'
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ module Bidu
2
+ class PeriodParser
3
+ class << self
4
+ def parse(period)
5
+ return unless period
6
+ return period if period.is_a?(Integer)
7
+ new(period).to_seconds
8
+ end
9
+ end
10
+
11
+ def initialize(period)
12
+ @period = period
13
+ end
14
+
15
+ def to_seconds
16
+ return unless period.match(/^\d+(years|months|days|hours|minutes|seconds)?/)
17
+ type.blank? ? value.seconds : value.public_send(type)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :period
23
+
24
+ def value
25
+ @period_value ||= period.gsub(/\D+/, '').to_i
26
+ end
27
+
28
+ def type
29
+ @period_type ||= period.gsub(/\d+/, '')
30
+ end
31
+ end
32
+ end
data/lib/bidu.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Bidu
2
+ require 'bidu/house'
3
+ end
@@ -0,0 +1,7 @@
1
+ module JsonParser
2
+ module TypeCast
3
+ def to_period(value)
4
+ Bidu::PeriodParser.parse(value)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Relation do
4
+ describe '#percentage' do
5
+ let(:error_number) { 1 }
6
+ let(:success_number) { 1 }
7
+
8
+ before do
9
+ Document.all.each(&:destroy)
10
+ error_number.times { Document.create(status: :error) }
11
+ success_number.times { Document.create(status: :success) }
12
+ end
13
+
14
+ context 'when there are 50% documents with error' do
15
+ it do
16
+ expect(Document.all.percentage(status: :error)).to eq(0.5)
17
+ end
18
+ end
19
+
20
+ context 'when there are 25% documents with error' do
21
+ let(:success_number) { 3 }
22
+
23
+ it do
24
+ expect(Document.all.percentage(status: :error)).to eq(0.25)
25
+ end
26
+ end
27
+
28
+ context 'when passing a sub scope' do
29
+ before do
30
+ Document.create(status: :on_going)
31
+ end
32
+
33
+ it 'does the math inside the scope' do
34
+ expect(Document.where(status: [:error, :success]).percentage(status: :error)).to eq(0.5)
35
+ end
36
+ end
37
+
38
+ context 'when passing a scope name instead of query' do
39
+ it 'does the math inside the scope' do
40
+ expect(Document.all.percentage(:with_error)).to eq(0.5)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,194 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bidu::House::ErrorReport do
4
+ let(:errors) { 1 }
5
+ let(:successes) { 1 }
6
+ let(:old_errors) { 2 }
7
+ let(:threshold) { 0.02 }
8
+ let(:period) { 1.day }
9
+ let(:external_key) { :external_id }
10
+ let(:options) do
11
+ {
12
+ period: period,
13
+ threshold: threshold,
14
+ scope: :with_error,
15
+ clazz: Document,
16
+ external_key: external_key
17
+ }
18
+ end
19
+ let(:subject) { described_class.new(options) }
20
+ before do
21
+ Document.all.each(&:destroy)
22
+ successes.times { Document.create status: :success }
23
+ errors.times do |i|
24
+ Document.create status: :error, external_id: 10 * successes + i, outter_external_id: i
25
+ end
26
+ old_errors.times { Document.create status: :error, created_at: 2.days.ago, updated_at: 2.days.ago }
27
+ end
28
+
29
+ describe '#status' do
30
+ context 'when are more errors than the allowed by the threshold' do
31
+ let(:errors) { 1 }
32
+ let(:successes) { 3 }
33
+ it { expect(subject.status).to eq(:error) }
34
+ end
35
+
36
+ context 'when the threshold is 0 and there are no errors' do
37
+ let(:errors) { 0 }
38
+ let(:threshold) { 0 }
39
+ it { expect(subject.status).to eq(:ok) }
40
+ end
41
+
42
+ context 'when the threshold is 100% and there is 100% error' do
43
+ let(:successes) { 0 }
44
+ let(:threshold) { 1 }
45
+ it { expect(subject.status).to eq(:ok) }
46
+ end
47
+
48
+ context 'when there are no documents' do
49
+ let(:successes) { 0 }
50
+ let(:errors) { 0 }
51
+ it { expect(subject.status).to eq(:ok) }
52
+ end
53
+
54
+ context 'when there are older errors out of the period' do
55
+ let(:threshold) { 0.6 }
56
+
57
+ it 'ignores the older errros' do
58
+ expect(subject.status).to eq(:ok)
59
+ end
60
+
61
+ context 'when passing a bigger period' do
62
+ let(:period) { 3.days }
63
+
64
+ it 'consider the older errros' do
65
+ expect(subject.status).to eq(:error)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'percentage' do
72
+ context 'when there are 25% erros' do
73
+ let(:errors) { 1 }
74
+ let(:successes) { 3 }
75
+ it { expect(subject.percentage).to eq(0.25) }
76
+ end
77
+
78
+ context 'when there are no errors' do
79
+ let(:errors) { 0 }
80
+ let(:threshold) { 0 }
81
+ it { expect(subject.percentage).to eq(0) }
82
+ end
83
+
84
+ context 'when there is 100% error' do
85
+ let(:successes) { 0 }
86
+ let(:threshold) { 1 }
87
+ it { expect(subject.percentage).to eq(1) }
88
+ end
89
+
90
+ context 'when there are no documents' do
91
+ let(:successes) { 0 }
92
+ let(:errors) { 0 }
93
+ it { expect(subject.percentage).to eq(0) }
94
+ end
95
+
96
+ context 'when there are older errors out of the period' do
97
+ let(:old_errors) { 2 }
98
+
99
+ it 'ignores the older errros' do
100
+ expect(subject.percentage).to eq(0.5)
101
+ end
102
+
103
+ context 'when passing a bigger period' do
104
+ let(:period) { 3.days }
105
+
106
+ it 'consider the older errros' do
107
+ expect(subject.percentage).to eq(0.75)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#scoped' do
114
+ context 'when there are 25% erros' do
115
+ let(:errors) { 1 }
116
+ let(:successes) { 3 }
117
+ it 'returns only the scoped documents' do
118
+ expect(subject.scoped.count).to eq(1)
119
+ end
120
+ end
121
+
122
+ context 'when there are no errors' do
123
+ let(:errors) { 0 }
124
+ let(:threshold) { 0 }
125
+ it { expect(subject.scoped).to be_empty }
126
+ end
127
+
128
+ context 'when there are no documents' do
129
+ let(:successes) { 0 }
130
+ let(:errors) { 0 }
131
+ it { expect(subject.scoped).to be_empty }
132
+ end
133
+
134
+ context 'when there are older errors out of the period' do
135
+ let(:old_errors) { 2 }
136
+
137
+ it 'ignores the older errros' do
138
+ expect(subject.scoped.count).to eq(1)
139
+ end
140
+
141
+ context 'when passing a bigger period' do
142
+ let(:period) { 3.days }
143
+
144
+ it 'consider the older errros' do
145
+ expect(subject.scoped.count).to eq(3)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#error?' do
152
+ context 'when errors percentage overcames threshold' do
153
+ it { expect(subject.error?).to be_truthy }
154
+ end
155
+
156
+ context 'when errors percentage does not overcames threshold' do
157
+ let(:errors) { 0 }
158
+ it { expect(subject.error?).to be_falsey }
159
+ end
160
+ end
161
+
162
+ describe '#as_json' do
163
+ context 'when there are 75% erros' do
164
+ let(:errors) { 3 }
165
+ let(:successes) { 1 }
166
+ let(:ids_expected) { [10, 11, 12] }
167
+ let(:expected) do
168
+ { ids: ids_expected, percentage: 0.75 }
169
+ end
170
+
171
+ it 'returns the external keys and error percentage' do
172
+ expect(subject.as_json).to eq(expected)
173
+ end
174
+
175
+ context 'when configurated with different external key' do
176
+ let(:external_key) { :outter_external_id }
177
+ let(:ids_expected) { [0, 1, 2] }
178
+
179
+ it 'returns the correct external keys' do
180
+ expect(subject.as_json).to eq(expected)
181
+ end
182
+ end
183
+
184
+ context 'when configurated without external key' do
185
+ before { options.delete(:external_key) }
186
+ let(:ids_expected) { Document.with_error.where('created_at > ?', 30.hours.ago).map(&:id) }
187
+
188
+ it 'returns the ids as default id' do
189
+ expect(subject.as_json).to eq(expected)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bidu::House::ReportBuilder do
4
+ let(:errors) { 1 }
5
+ let(:successes) { 3 }
6
+ let(:old_errors) { 2 }
7
+ let(:key) { :errors }
8
+ let(:threshold) { 0.02 }
9
+ let(:period) { 1.day }
10
+ let(:external_key) { :external_id }
11
+ let(:config) do
12
+ {
13
+ period: period,
14
+ threshold: threshold,
15
+ external_key: :external_id,
16
+ scope: :with_error,
17
+ id: key,
18
+ clazz: Document
19
+ }
20
+ end
21
+ let(:parameters) { {} }
22
+ let(:report) { subject.build(key, parameters) }
23
+ before do
24
+ subject.add_config(key, config)
25
+ Document.all.each(&:destroy)
26
+ successes.times { |i| Document.create status: :success, external_id: 30+i }
27
+ errors.times { |i| Document.create status: :error, external_id: 10+i }
28
+ old_errors.times do |i|
29
+ Document.create status: :error, external_id: 20+i, created_at: 2.days.ago, updated_at: 2.days.ago
30
+ end
31
+ end
32
+
33
+ describe '#build' do
34
+ let(:ids) { [ 10 ] }
35
+ it do
36
+ expect(report).to be_a(Bidu::House::ErrorReport)
37
+ end
38
+
39
+ it 'builds the report using the given configuration' do
40
+ expect(report.as_json).to eq( ids: ids, percentage: 0.25 )
41
+ expect(report.error?).to be_truthy
42
+ end
43
+
44
+ context 'when passing a custom threshold parameter' do
45
+ let(:parameters) { { threshold: 1 } }
46
+
47
+ it 'uses custom threshold parameter' do
48
+ expect(report.error?).to be_falsey
49
+ end
50
+ end
51
+
52
+ context 'when passing a custom period parameter' do
53
+ let(:parameters) { { threshold: 0.4, period: 10.days } }
54
+
55
+ it 'uses custom period parameter' do
56
+ expect(report.error?).to be_truthy
57
+ end
58
+ end
59
+
60
+ context 'when passing a custom other parameters' do
61
+ let(:parameters) do
62
+ { scope: :with_success, clazz: Bidu::House::ErrorReport, external_key: :id, id: :failures }
63
+ end
64
+
65
+ it 'ignores the non customizable parameters' do
66
+ expect(report.as_json).to eq( ids: ids, percentage: 0.25 )
67
+ expect(report.error?).to be_truthy
68
+ expect(report.id).to eq(:errors)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bidu::House::StatusBuilder do
4
+ let(:errors) { 1 }
5
+ let(:successes) { 3 }
6
+ let(:old_errors) { 2 }
7
+ let(:key) { :errors }
8
+ let(:threshold) { 0.02 }
9
+ let(:period) { 1.day }
10
+ let(:external_key) { :external_id }
11
+ let(:config) do
12
+ {
13
+ period: period,
14
+ threshold: threshold,
15
+ scope: :with_error,
16
+ clazz: Document,
17
+ id: :failures,
18
+ external_key: :external_id,
19
+ on: key
20
+ }
21
+ end
22
+ let(:parameters) { {} }
23
+ let(:status) { subject.build(key, parameters) }
24
+ before do
25
+ subject.add_report_config(key, config)
26
+ Document.all.each(&:destroy)
27
+ successes.times { |i| Document.create status: :success, external_id: 30+i }
28
+ errors.times { |i| Document.create status: :error, external_id: 10+i }
29
+ old_errors.times do |i|
30
+ Document.create status: :error, external_id: 20+i, created_at: 2.days.ago, updated_at: 2.days.ago
31
+ end
32
+ end
33
+
34
+ describe '#build' do
35
+ let(:ids) { [ 10 ] }
36
+ let(:status_expected) { :error }
37
+ let(:percentage) { 0.25 }
38
+ let(:json_expected) do
39
+ {
40
+ status: status_expected,
41
+ failures: {
42
+ ids: ids,
43
+ percentage: percentage
44
+ }
45
+ }
46
+ end
47
+
48
+ it do
49
+ expect(status).to be_a(Bidu::House::Status)
50
+ end
51
+
52
+ context 'when not specifying where to report' do
53
+ let(:key) {}
54
+
55
+ it 'register report under default' do
56
+ expect(subject.build(:default).as_json).to eq(json_expected)
57
+ end
58
+ end
59
+
60
+ it 'builds the report using the given configuration' do
61
+ expect(status.as_json).to eq(json_expected)
62
+ end
63
+
64
+ context 'when passing a custom threshold parameter' do
65
+ let(:parameters) { { threshold: 1 } }
66
+ let(:status_expected) { :ok }
67
+
68
+ it 'uses custom threshold parameter' do
69
+ expect(status.as_json).to eq(json_expected)
70
+ end
71
+ end
72
+
73
+ context 'when passing a custom period parameter' do
74
+ let(:ids) { [ 10, 20, 21 ] }
75
+ let(:percentage) { 0.5 }
76
+ let(:parameters) { { threshold: 0.4, period: 10.days } }
77
+
78
+ it 'uses custom period parameter' do
79
+ expect(status.as_json).to eq(json_expected)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bidu::House::Status do
4
+ let(:threshold) { 0.02 }
5
+ let(:period) { 1.day }
6
+ let(:report_options) do
7
+ {
8
+ period: period,
9
+ threshold: threshold,
10
+ scope: :with_error,
11
+ clazz: Document,
12
+ id: :errors
13
+ }
14
+ end
15
+ let(:success_options) do
16
+ report_options.merge(
17
+ scope: :with_success,
18
+ id: :success
19
+ )
20
+ end
21
+ let(:errors) { 0 }
22
+ let(:successes) { 1 }
23
+ let(:error_report) { Bidu::House::ErrorReport.new(report_options) }
24
+ let(:success_report) do
25
+ Bidu::House::ErrorReport.new(success_options)
26
+ end
27
+ let(:reports) { [ error_report ] }
28
+ let(:subject) { described_class.new(reports) }
29
+ before do
30
+ Document.all.each(&:destroy)
31
+ errors.times { Document.create status: :error }
32
+ successes.times { Document.create status: :success }
33
+ end
34
+
35
+ describe '#as_json' do
36
+ let(:status_json) { subject.as_json }
37
+ let(:status) { status_json[:status] }
38
+
39
+ context 'when report is ok' do
40
+ it 'returns a json with ok' do
41
+ expect(status).to eq(:ok)
42
+ end
43
+
44
+ it 'returns the report json' do
45
+
46
+ end
47
+ end
48
+
49
+ context 'when report is not ok' do
50
+ let(:errors) { 1 }
51
+
52
+ it 'returns a json with error' do
53
+ expect(status).to eq(:error)
54
+ end
55
+ end
56
+
57
+ context 'when there are both success and error reports' do
58
+ let(:success_report) do
59
+ Bidu::House::ErrorReport.new(report_options.merge(scope: :with_success))
60
+ end
61
+ let(:reports) { [ success_report, error_report ] }
62
+
63
+ it 'returns a json with error' do
64
+ expect(status).to eq(:error)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#status' do
70
+ context 'when report is ok' do
71
+ it do
72
+ expect(subject.status).to eq(:ok)
73
+ end
74
+ end
75
+
76
+ context 'when report is not ok' do
77
+ let(:errors) { 1 }
78
+
79
+ it do
80
+ expect(subject.status).to eq(:error)
81
+ end
82
+ end
83
+
84
+ context 'when there are both success and error reports' do
85
+ let(:reports) { [ success_report, error_report ] }
86
+
87
+ it do
88
+ expect(subject.status).to eq(:error)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#as_json' do
94
+ let(:errors) { 3 }
95
+ let(:ids) { Document.with_error.map(&:id) }
96
+ let(:expected) do
97
+ {
98
+ status: :error,
99
+ errors: {
100
+ ids: ids,
101
+ percentage: 0.75
102
+ }
103
+ }
104
+ end
105
+
106
+ it 'creates a summary of the reports' do
107
+ expect(subject.as_json).to eq(expected)
108
+ end
109
+
110
+ context 'when there are both success and error reports' do
111
+ let(:success_ids) { Document.with_success.map(&:id) }
112
+ let(:expected) do
113
+ {
114
+ status: :error,
115
+ errors: {
116
+ ids: ids,
117
+ percentage: 0.75
118
+ },
119
+ success: {
120
+ ids: success_ids,
121
+ percentage: 0.25
122
+ }
123
+ }
124
+ end
125
+ let(:reports) { [ success_report, error_report ] }
126
+
127
+ it 'creates a summary of all the reports' do
128
+ expect(subject.as_json).to eq(expected)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bidu::PeriodParser do
4
+ shared_examples 'a class who knows how to parse time' do |tests|
5
+ tests.each do |string, expected|
6
+ it "parses #{string} into #{expected} seconds" do
7
+ expect(described_class.parse(string)).to eq(expected)
8
+ end
9
+ end
10
+ end
11
+
12
+ it_behaves_like 'a class who knows how to parse time', {
13
+ '3' => 3.seconds,
14
+ '3seconds' => 3.seconds,
15
+ '3minutes' => 3.minutes,
16
+ '3hours' => 3.hours,
17
+ '3days' => 3.days,
18
+ '3months' => 3.months,
19
+ '3years' => 3.years
20
+ }
21
+
22
+ context 'when value is already a period' do
23
+ it 'returns the value itself' do
24
+ expect(described_class.parse(3.minutes)).to eq(3.minutes)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'bidu/house'
5
+
6
+ require 'active_record'
7
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
8
+
9
+ support_files = File.expand_path("spec/support/**/*.rb")
10
+ Dir[support_files].each { |file| require file }
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+ config.filter_run_excluding :integration unless ENV['ALL']
17
+
18
+ config.order = 'random'
19
+
20
+ config.before do
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support'
2
+
3
+ module FixtureHelpers
4
+ def load_fixture_file(filename)
5
+ File.read (['./spec/', 'fixtures', filename].join('/'))
6
+ end
7
+
8
+ def load_json_fixture_file(filename)
9
+ cached_json_fixture_file(filename)
10
+ end
11
+
12
+ private
13
+
14
+ def cached_json_fixture_file(filename)
15
+ ActiveSupport::JSON.decode(load_fixture_file(filename))
16
+ end
17
+ end
18
+
19
+ RSpec.configuration.include FixtureHelpers
@@ -0,0 +1,4 @@
1
+ class Document < ActiveRecord::Base
2
+ scope :with_error, proc { where(status: :error) }
3
+ scope :with_success, proc { where(status: :success) }
4
+ end
@@ -0,0 +1,10 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :documents, :force => true do |t|
5
+ t.string :status
6
+ t.integer :external_id
7
+ t.integer :outter_external_id
8
+ t.timestamps null: true
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bidu-house
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bidu Dev's Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concern_builder
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bidu-core_ext
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json_parser
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '1.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '2.14'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '2.14'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Gem for easy health check
154
+ email:
155
+ - dev@bidu.com.br
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - .gitignore
161
+ - .rspec
162
+ - Gemfile
163
+ - Gemfile.lock
164
+ - LICENSE
165
+ - README.md
166
+ - Rakefile
167
+ - house.gemspec
168
+ - lib/active_record/relation_ext.rb
169
+ - lib/bidu.rb
170
+ - lib/bidu/house.rb
171
+ - lib/bidu/house/class_methods.rb
172
+ - lib/bidu/house/concern.rb
173
+ - lib/bidu/house/error_report.rb
174
+ - lib/bidu/house/report_builder.rb
175
+ - lib/bidu/house/status.rb
176
+ - lib/bidu/house/status_builder.rb
177
+ - lib/bidu/house/version.rb
178
+ - lib/bidu/period_parser.rb
179
+ - lib/json_parser/type_cast_ext.rb
180
+ - spec/lib/active_record/relation_spec.rb
181
+ - spec/lib/bidu/house/error_report_spec.rb
182
+ - spec/lib/bidu/house/report_builder_spec.rb
183
+ - spec/lib/bidu/house/status_builder_spec.rb
184
+ - spec/lib/bidu/house/status_spec.rb
185
+ - spec/lib/bidu/period_parser_spec.rb
186
+ - spec/spec_helper.rb
187
+ - spec/support/fixture_helpers.rb
188
+ - spec/support/models/document.rb
189
+ - spec/support/schema.rb
190
+ homepage: https://github.com/Bidu/house
191
+ licenses: []
192
+ metadata: {}
193
+ post_install_message:
194
+ rdoc_options: []
195
+ require_paths:
196
+ - lib
197
+ required_ruby_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - '>='
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ requirements: []
208
+ rubyforge_project:
209
+ rubygems_version: 2.4.6
210
+ signing_key:
211
+ specification_version: 4
212
+ summary: Gem for easy health check
213
+ test_files: []