bidu-house 0.1.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 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: []