double_entry 0.10.0 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/.rubocop.yml +55 -0
- data/.travis.yml +23 -12
- data/README.md +5 -1
- data/Rakefile +8 -3
- data/double_entry.gemspec +4 -3
- data/lib/active_record/locking_extensions.rb +28 -40
- data/lib/active_record/locking_extensions/log_subscriber.rb +4 -4
- data/lib/double_entry.rb +0 -2
- data/lib/double_entry/account.rb +13 -16
- data/lib/double_entry/account_balance.rb +0 -4
- data/lib/double_entry/balance_calculator.rb +4 -5
- data/lib/double_entry/configurable.rb +0 -2
- data/lib/double_entry/configuration.rb +2 -3
- data/lib/double_entry/errors.rb +2 -2
- data/lib/double_entry/line.rb +13 -16
- data/lib/double_entry/locking.rb +13 -18
- data/lib/double_entry/reporting.rb +2 -3
- data/lib/double_entry/reporting/aggregate.rb +90 -88
- data/lib/double_entry/reporting/aggregate_array.rb +58 -58
- data/lib/double_entry/reporting/day_range.rb +37 -35
- data/lib/double_entry/reporting/hour_range.rb +40 -37
- data/lib/double_entry/reporting/line_aggregate.rb +27 -28
- data/lib/double_entry/reporting/month_range.rb +67 -67
- data/lib/double_entry/reporting/time_range.rb +40 -38
- data/lib/double_entry/reporting/time_range_array.rb +3 -5
- data/lib/double_entry/reporting/week_range.rb +77 -78
- data/lib/double_entry/reporting/year_range.rb +27 -27
- data/lib/double_entry/transfer.rb +14 -15
- data/lib/double_entry/validation/line_check.rb +92 -86
- data/lib/double_entry/version.rb +1 -1
- data/lib/generators/double_entry/install/install_generator.rb +1 -2
- data/lib/generators/double_entry/install/templates/migration.rb +0 -2
- data/script/jack_hammer +1 -1
- data/spec/active_record/locking_extensions_spec.rb +45 -38
- data/spec/double_entry/account_balance_spec.rb +4 -5
- data/spec/double_entry/account_spec.rb +43 -44
- data/spec/double_entry/balance_calculator_spec.rb +6 -8
- data/spec/double_entry/configuration_spec.rb +14 -16
- data/spec/double_entry/line_spec.rb +25 -26
- data/spec/double_entry/locking_spec.rb +34 -39
- data/spec/double_entry/reporting/aggregate_array_spec.rb +8 -10
- data/spec/double_entry/reporting/aggregate_spec.rb +84 -44
- data/spec/double_entry/reporting/line_aggregate_spec.rb +7 -6
- data/spec/double_entry/reporting/month_range_spec.rb +109 -103
- data/spec/double_entry/reporting/time_range_array_spec.rb +145 -135
- data/spec/double_entry/reporting/time_range_spec.rb +36 -35
- data/spec/double_entry/reporting/week_range_spec.rb +82 -76
- data/spec/double_entry/reporting_spec.rb +9 -13
- data/spec/double_entry/transfer_spec.rb +13 -15
- data/spec/double_entry/validation/line_check_spec.rb +73 -79
- data/spec/double_entry_spec.rb +65 -68
- data/spec/generators/double_entry/install/install_generator_spec.rb +7 -10
- data/spec/spec_helper.rb +68 -10
- data/spec/support/accounts.rb +2 -4
- data/spec/support/double_entry_spec_helper.rb +4 -4
- data/spec/support/gemfiles/Gemfile.rails-3.2.x +1 -0
- metadata +31 -2
@@ -1,10 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
describe DoubleEntry::BalanceCalculator do
|
5
2
|
|
3
|
+
RSpec.describe DoubleEntry::BalanceCalculator do
|
6
4
|
describe '#calculate' do
|
7
|
-
let(:account) { DoubleEntry
|
5
|
+
let(:account) { DoubleEntry.account(:test, :scope => scope) }
|
8
6
|
let(:scope) { User.make! }
|
9
7
|
let(:from) { nil }
|
10
8
|
let(:to) { nil }
|
@@ -42,7 +40,7 @@ describe DoubleEntry::BalanceCalculator do
|
|
42
40
|
|
43
41
|
it 'ignores the time range when summing the lines' do
|
44
42
|
expect(relation).to_not have_received(:where).with(
|
45
|
-
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000')
|
43
|
+
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000'),
|
46
44
|
)
|
47
45
|
expect(relation).to_not have_received(:sum)
|
48
46
|
end
|
@@ -55,7 +53,7 @@ describe DoubleEntry::BalanceCalculator do
|
|
55
53
|
|
56
54
|
it 'scopes the lines summed to times within the given range' do
|
57
55
|
expect(relation).to have_received(:where).with(
|
58
|
-
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000')
|
56
|
+
:created_at => Time.parse('2014-06-19 10:09:18 +1000')..Time.parse('2014-06-19 20:09:18 +1000'),
|
59
57
|
)
|
60
58
|
expect(relation).to have_received(:sum).with(:amount)
|
61
59
|
end
|
@@ -72,10 +70,10 @@ describe DoubleEntry::BalanceCalculator do
|
|
72
70
|
end
|
73
71
|
|
74
72
|
context 'when a list of codes is provided' do
|
75
|
-
let(:codes) {
|
73
|
+
let(:codes) { %w(code1 code2) }
|
76
74
|
|
77
75
|
it 'scopes the lines summed by the given codes' do
|
78
|
-
expect(relation).to have_received(:where).with(:code =>
|
76
|
+
expect(relation).to have_received(:where).with(:code => %w(code1 code2))
|
79
77
|
expect(relation).to have_received(:sum).with(:amount)
|
80
78
|
end
|
81
79
|
end
|
@@ -1,28 +1,26 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
describe DoubleEntry::Configuration do
|
4
|
-
|
2
|
+
RSpec.describe DoubleEntry::Configuration do
|
5
3
|
its(:accounts) { should be_a DoubleEntry::Account::Set }
|
6
4
|
its(:transfers) { should be_a DoubleEntry::Transfer::Set }
|
7
5
|
|
8
|
-
describe
|
9
|
-
context
|
6
|
+
describe 'max lengths' do
|
7
|
+
context 'given a max length has not been set' do
|
10
8
|
its(:code_max_length) { should be 47 }
|
11
9
|
its(:scope_identifier_max_length) { should be 23 }
|
12
10
|
its(:account_identifier_max_length) { should be 31 }
|
13
11
|
end
|
14
12
|
|
15
|
-
context
|
13
|
+
context 'given a code max length of 10 has been set' do
|
16
14
|
before { subject.code_max_length = 10 }
|
17
15
|
its(:code_max_length) { should be 10 }
|
18
16
|
end
|
19
17
|
|
20
|
-
context
|
18
|
+
context 'given a scope identifier max length of 11 has been set' do
|
21
19
|
before { subject.scope_identifier_max_length = 11 }
|
22
20
|
its(:scope_identifier_max_length) { should be 11 }
|
23
21
|
end
|
24
22
|
|
25
|
-
context
|
23
|
+
context 'given an account identifier max length of 9 has been set' do
|
26
24
|
before { subject.account_identifier_max_length = 9 }
|
27
25
|
its(:account_identifier_max_length) { should be 9 }
|
28
26
|
end
|
@@ -34,19 +32,19 @@ describe DoubleEntry::Configuration do
|
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
37
|
-
describe
|
38
|
-
it
|
39
|
-
expect
|
35
|
+
describe '#define_accounts' do
|
36
|
+
it 'yields the accounts set' do
|
37
|
+
expect do |block|
|
40
38
|
subject.define_accounts(&block)
|
41
|
-
|
39
|
+
end.to yield_with_args(be_a DoubleEntry::Account::Set)
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
|
-
describe
|
46
|
-
it
|
47
|
-
expect
|
43
|
+
describe '#define_transfers' do
|
44
|
+
it 'yields the transfers set' do
|
45
|
+
expect do |block|
|
48
46
|
subject.define_transfers(&block)
|
49
|
-
|
47
|
+
end.to yield_with_args(be_a DoubleEntry::Transfer::Set)
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
expect(DoubleEntry::Line.table_name).to eq "double_entry_lines"
|
2
|
+
RSpec.describe DoubleEntry::Line do
|
3
|
+
it 'has a table name prefixed with double_entry_' do
|
4
|
+
expect(DoubleEntry::Line.table_name).to eq 'double_entry_lines'
|
6
5
|
end
|
7
6
|
|
8
|
-
describe
|
9
|
-
let(:line_to_persist)
|
7
|
+
describe 'persistance' do
|
8
|
+
let(:line_to_persist) do
|
10
9
|
DoubleEntry::Line.new(
|
11
10
|
:amount => Money.new(10_00),
|
12
11
|
:balance => Money.zero,
|
@@ -14,9 +13,9 @@ describe DoubleEntry::Line do
|
|
14
13
|
:partner_account => partner_account,
|
15
14
|
:code => code,
|
16
15
|
)
|
17
|
-
|
18
|
-
let(:account) { DoubleEntry.account(:test, :scope =>
|
19
|
-
let(:partner_account) { DoubleEntry.account(:test, :scope =>
|
16
|
+
end
|
17
|
+
let(:account) { DoubleEntry.account(:test, :scope => '17') }
|
18
|
+
let(:partner_account) { DoubleEntry.account(:test, :scope => '72') }
|
20
19
|
let(:code) { :test_code }
|
21
20
|
|
22
21
|
subject(:persisted_line) do
|
@@ -24,33 +23,33 @@ describe DoubleEntry::Line do
|
|
24
23
|
line_to_persist.reload
|
25
24
|
end
|
26
25
|
|
27
|
-
describe
|
28
|
-
context
|
26
|
+
describe 'attributes' do
|
27
|
+
context 'given code = :the_code' do
|
29
28
|
let(:code) { :the_code }
|
30
29
|
its(:code) { should eq :the_code }
|
31
30
|
end
|
32
31
|
|
33
|
-
context
|
32
|
+
context 'given code = nil' do
|
34
33
|
let(:code) { nil }
|
35
34
|
specify { expect { line_to_persist.save! }.to raise_error }
|
36
35
|
end
|
37
36
|
|
38
|
-
context
|
39
|
-
let(:account) { DoubleEntry.account(:test, :scope =>
|
40
|
-
its(
|
41
|
-
its(
|
37
|
+
context 'given account = :test, 54 ' do
|
38
|
+
let(:account) { DoubleEntry.account(:test, :scope => '54') }
|
39
|
+
its('account.account.identifier') { should eq :test }
|
40
|
+
its('account.scope') { should eq '54' }
|
42
41
|
end
|
43
42
|
|
44
|
-
context
|
45
|
-
let(:partner_account) { DoubleEntry.account(:test, :scope =>
|
46
|
-
its(
|
47
|
-
its(
|
43
|
+
context 'given partner_account = :test, 91 ' do
|
44
|
+
let(:partner_account) { DoubleEntry.account(:test, :scope => '91') }
|
45
|
+
its('partner_account.account.identifier') { should eq :test }
|
46
|
+
its('partner_account.scope') { should eq '91' }
|
48
47
|
end
|
49
48
|
|
50
|
-
context
|
51
|
-
let(:account) { DoubleEntry.account(:btc_test, :scope =>
|
52
|
-
let(:partner_account) { DoubleEntry.account(:btc_test, :scope =>
|
53
|
-
its(:currency) { should eq
|
49
|
+
context 'currency' do
|
50
|
+
let(:account) { DoubleEntry.account(:btc_test, :scope => '17') }
|
51
|
+
let(:partner_account) { DoubleEntry.account(:btc_test, :scope => '72') }
|
52
|
+
its(:currency) { should eq 'BTC' }
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
@@ -74,8 +73,8 @@ describe DoubleEntry::Line do
|
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
77
|
-
it
|
78
|
-
expect(DoubleEntry::Line.table_name).to eq
|
76
|
+
it 'has a table name prefixed with double_entry_' do
|
77
|
+
expect(DoubleEntry::Line.table_name).to eq 'double_entry_lines'
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
describe DoubleEntry::Locking do
|
5
2
|
|
3
|
+
RSpec.describe DoubleEntry::Locking do
|
6
4
|
before do
|
7
5
|
@config_accounts = DoubleEntry.configuration.accounts
|
8
6
|
@config_transfers = DoubleEntry.configuration.transfers
|
@@ -33,16 +31,16 @@ describe DoubleEntry::Locking do
|
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
@account_a = DoubleEntry.account(:account_a, :scope =>
|
37
|
-
@account_b = DoubleEntry.account(:account_b, :scope =>
|
38
|
-
@account_c = DoubleEntry.account(:account_c, :scope =>
|
39
|
-
@account_d = DoubleEntry.account(:account_d, :scope =>
|
34
|
+
@account_a = DoubleEntry.account(:account_a, :scope => '1')
|
35
|
+
@account_b = DoubleEntry.account(:account_b, :scope => '2')
|
36
|
+
@account_c = DoubleEntry.account(:account_c, :scope => '3')
|
37
|
+
@account_d = DoubleEntry.account(:account_d, :scope => '4')
|
40
38
|
@account_e = DoubleEntry.account(:account_e)
|
41
39
|
end
|
42
40
|
|
43
|
-
it
|
41
|
+
it 'creates missing account balance records' do
|
44
42
|
expect do
|
45
|
-
DoubleEntry::Locking.lock_accounts(@account_a) {
|
43
|
+
DoubleEntry::Locking.lock_accounts(@account_a) {}
|
46
44
|
end.to change(DoubleEntry::AccountBalance, :count).by(1)
|
47
45
|
|
48
46
|
account_balance = DoubleEntry::AccountBalance.find_by_account(@account_a)
|
@@ -50,7 +48,7 @@ describe DoubleEntry::Locking do
|
|
50
48
|
expect(account_balance.balance).to eq Money.new(0)
|
51
49
|
end
|
52
50
|
|
53
|
-
it
|
51
|
+
it 'takes the balance for new account balance records from the lines table' do
|
54
52
|
DoubleEntry::Line.create!(
|
55
53
|
:account => @account_a,
|
56
54
|
:partner_account => @account_b,
|
@@ -67,7 +65,7 @@ describe DoubleEntry::Locking do
|
|
67
65
|
)
|
68
66
|
|
69
67
|
expect do
|
70
|
-
DoubleEntry::Locking.lock_accounts(@account_a) {
|
68
|
+
DoubleEntry::Locking.lock_accounts(@account_a) {}
|
71
69
|
end.to change(DoubleEntry::AccountBalance, :count).by(1)
|
72
70
|
|
73
71
|
account_balance = DoubleEntry::AccountBalance.find_by_account(@account_a)
|
@@ -75,43 +73,43 @@ describe DoubleEntry::Locking do
|
|
75
73
|
expect(account_balance.balance).to eq Money.new(10_00)
|
76
74
|
end
|
77
75
|
|
78
|
-
it
|
79
|
-
expect
|
76
|
+
it 'prohibits locking inside a regular transaction' do
|
77
|
+
expect do
|
80
78
|
DoubleEntry::AccountBalance.transaction do
|
81
79
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
82
80
|
end
|
83
81
|
end
|
84
|
-
|
82
|
+
end.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
85
83
|
end
|
86
84
|
|
87
|
-
it
|
88
|
-
expect
|
85
|
+
it 'prohibits a transfer inside a regular transaction' do
|
86
|
+
expect do
|
89
87
|
DoubleEntry::AccountBalance.transaction do
|
90
88
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
91
89
|
end
|
92
|
-
|
90
|
+
end.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
93
91
|
end
|
94
92
|
|
95
93
|
it "allows a transfer inside a lock if we've locked the transaction accounts" do
|
96
|
-
expect
|
94
|
+
expect do
|
97
95
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
98
96
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
99
97
|
end
|
100
|
-
|
98
|
+
end.to_not raise_error
|
101
99
|
end
|
102
100
|
|
103
101
|
it "does not allow a transfer inside a lock if the right locks aren't held" do
|
104
|
-
expect
|
102
|
+
expect do
|
105
103
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_c) do
|
106
104
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
107
105
|
end
|
108
|
-
|
106
|
+
end.to raise_error(DoubleEntry::Locking::LockNotHeld, 'No lock held for account: account_b, scope 2')
|
109
107
|
end
|
110
108
|
|
111
|
-
it
|
109
|
+
it 'allows nested locks if the outer lock locks all the accounts' do
|
112
110
|
expect do
|
113
111
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
114
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {
|
112
|
+
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {}
|
115
113
|
end
|
116
114
|
end.to_not raise_error
|
117
115
|
end
|
@@ -119,15 +117,15 @@ describe DoubleEntry::Locking do
|
|
119
117
|
it "prohibits nested locks if the out lock doesn't lock all the accounts" do
|
120
118
|
expect do
|
121
119
|
DoubleEntry::Locking.lock_accounts(@account_a) do
|
122
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {
|
120
|
+
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) {}
|
123
121
|
end
|
124
|
-
end.to raise_error(DoubleEntry::Locking::LockNotHeld,
|
122
|
+
end.to raise_error(DoubleEntry::Locking::LockNotHeld, 'No lock held for account: account_b, scope 2')
|
125
123
|
end
|
126
124
|
|
127
|
-
it
|
125
|
+
it 'rolls back a locking transaction' do
|
128
126
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
129
127
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
130
|
-
|
128
|
+
fail ActiveRecord::Rollback
|
131
129
|
end
|
132
130
|
expect(DoubleEntry.balance(@account_a)).to eq Money.new(0)
|
133
131
|
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
@@ -137,27 +135,24 @@ describe DoubleEntry::Locking do
|
|
137
135
|
expect do
|
138
136
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
139
137
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
140
|
-
|
138
|
+
fail 'Yeah, right'
|
141
139
|
end
|
142
|
-
end.to raise_error(
|
140
|
+
end.to raise_error('Yeah, right')
|
143
141
|
expect(DoubleEntry.balance(@account_a)).to eq Money.new(0)
|
144
142
|
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
145
143
|
end
|
146
144
|
|
147
|
-
it
|
145
|
+
it 'allows locking a scoped account and a non scoped account' do
|
148
146
|
expect do
|
149
|
-
DoubleEntry::Locking.lock_accounts(@account_d, @account_e)
|
150
|
-
# do nothing
|
151
|
-
end
|
147
|
+
DoubleEntry::Locking.lock_accounts(@account_d, @account_e) {}
|
152
148
|
end.to_not raise_error
|
153
149
|
end
|
154
150
|
|
155
151
|
# sqlite cannot handle these cases so they don't run when DB=sqlite
|
156
|
-
describe
|
157
|
-
|
158
|
-
it "allows multiple threads to lock at the same time" do
|
152
|
+
describe 'concurrent locking', :unless => ENV['DB'] == 'sqlite' do
|
153
|
+
it 'allows multiple threads to lock at the same time' do
|
159
154
|
expect do
|
160
|
-
threads =
|
155
|
+
threads = []
|
161
156
|
|
162
157
|
threads << Thread.new do
|
163
158
|
sleep 0.05
|
@@ -177,8 +172,8 @@ describe DoubleEntry::Locking do
|
|
177
172
|
end.to_not raise_error
|
178
173
|
end
|
179
174
|
|
180
|
-
it
|
181
|
-
threads =
|
175
|
+
it 'allows multiple threads to lock accounts without balances at the same time' do
|
176
|
+
threads = []
|
182
177
|
expect do
|
183
178
|
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { sleep 0.1 } }
|
184
179
|
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_c, @account_d) { sleep 0.1 } }
|
@@ -1,8 +1,6 @@
|
|
1
|
-
require 'spec_helper'
|
2
1
|
module DoubleEntry
|
3
2
|
module Reporting
|
4
|
-
describe AggregateArray do
|
5
|
-
|
3
|
+
RSpec.describe AggregateArray do
|
6
4
|
let(:user) { User.make! }
|
7
5
|
let(:start) { nil }
|
8
6
|
let(:finish) { nil }
|
@@ -10,7 +8,7 @@ module DoubleEntry
|
|
10
8
|
let(:function) { :sum }
|
11
9
|
let(:account) { :savings }
|
12
10
|
let(:transfer_code) { :bonus }
|
13
|
-
subject(:aggregate_array)
|
11
|
+
subject(:aggregate_array) do
|
14
12
|
Reporting.aggregate_array(
|
15
13
|
function,
|
16
14
|
account,
|
@@ -19,7 +17,7 @@ module DoubleEntry
|
|
19
17
|
:start => start,
|
20
18
|
:finish => finish,
|
21
19
|
)
|
22
|
-
|
20
|
+
end
|
23
21
|
|
24
22
|
context 'given a deposit was made in 2007 and 2008' do
|
25
23
|
before do
|
@@ -37,7 +35,7 @@ module DoubleEntry
|
|
37
35
|
context 'when called with range type of "year"' do
|
38
36
|
let(:range_type) { 'year' }
|
39
37
|
let(:start) { '2006-08-03' }
|
40
|
-
it { should eq [
|
38
|
+
it { should eq [Money.zero, Money.new(10_00), Money.new(20_00), Money.zero] }
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
@@ -56,7 +54,7 @@ module DoubleEntry
|
|
56
54
|
let(:range_type) { 'month' }
|
57
55
|
let(:start) { '2006-09-01' }
|
58
56
|
let(:finish) { '2007-01-02' }
|
59
|
-
it { should eq [
|
57
|
+
it { should eq [Money.zero, Money.new(10_00), Money.zero, Money.new(20_00), Money.zero] }
|
60
58
|
end
|
61
59
|
|
62
60
|
context 'given the date is 2007-02-02' do
|
@@ -65,7 +63,7 @@ module DoubleEntry
|
|
65
63
|
context 'when called with range type of "month"' do
|
66
64
|
let(:range_type) { 'month' }
|
67
65
|
let(:start) { '2006-08-03' }
|
68
|
-
it { should eq [
|
66
|
+
it { should eq [Money.zero, Money.zero, Money.new(10_00), Money.zero, Money.new(20_00), Money.zero, Money.zero] }
|
69
67
|
end
|
70
68
|
end
|
71
69
|
end
|
@@ -81,7 +79,7 @@ module DoubleEntry
|
|
81
79
|
perform_btc_deposit(user, 100_000_000)
|
82
80
|
end
|
83
81
|
|
84
|
-
it { should eq [
|
82
|
+
it { should eq [Money.new(200_000_000, :btc)] }
|
85
83
|
end
|
86
84
|
|
87
85
|
context 'when called with range type of "invalid_and_should_not_work"' do
|
@@ -96,7 +94,7 @@ module DoubleEntry
|
|
96
94
|
let(:start) { '2006-08-03' }
|
97
95
|
let(:function) { :invalid_function }
|
98
96
|
it 'raises an AggregateFunctionNotSupported error' do
|
99
|
-
expect{ aggregate_array }.to raise_error AggregateFunctionNotSupported
|
97
|
+
expect { aggregate_array }.to raise_error AggregateFunctionNotSupported
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'spec_helper'
|
3
2
|
module DoubleEntry
|
4
3
|
module Reporting
|
5
|
-
describe Aggregate do
|
6
|
-
|
4
|
+
RSpec.describe Aggregate do
|
7
5
|
let(:user) { User.make! }
|
8
6
|
let(:expected_weekly_average) do
|
9
7
|
(Money.new(20_00) + Money.new(40_00) + Money.new(50_00)) / 3
|
@@ -35,64 +33,84 @@ module DoubleEntry
|
|
35
33
|
end
|
36
34
|
|
37
35
|
it 'should store the aggregate for quick retrieval' do
|
38
|
-
Reporting.aggregate(:sum, :savings, :bonus,
|
39
|
-
:range => TimeRange.make(:year => 2009, :month => 10))
|
36
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => TimeRange.make(:year => 2009, :month => 10))
|
40
37
|
expect(LineAggregate.count).to eq 1
|
41
38
|
end
|
42
39
|
|
43
40
|
it 'should only store the aggregate once if it is requested more than once' do
|
44
|
-
Reporting.aggregate(:sum, :savings, :bonus,
|
45
|
-
|
46
|
-
Reporting.aggregate(:sum, :savings, :bonus,
|
47
|
-
:range => TimeRange.make(:year => 2009, :month => 9))
|
48
|
-
Reporting.aggregate(:sum, :savings, :bonus,
|
49
|
-
:range => TimeRange.make(:year => 2009, :month => 10))
|
41
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => TimeRange.make(:year => 2009, :month => 9))
|
42
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => TimeRange.make(:year => 2009, :month => 9))
|
43
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => TimeRange.make(:year => 2009, :month => 10))
|
50
44
|
expect(LineAggregate.count).to eq 2
|
51
45
|
end
|
52
46
|
|
53
47
|
it 'should calculate the complete year correctly' do
|
54
48
|
expect(
|
55
|
-
Reporting.aggregate(
|
49
|
+
Reporting.aggregate(
|
50
|
+
:sum, :savings, :bonus,
|
51
|
+
:range => TimeRange.make(:year => 2009)
|
52
|
+
),
|
56
53
|
).to eq Money.new(200_00)
|
57
54
|
end
|
58
55
|
|
59
56
|
it 'should calculate seperate months correctly' do
|
60
57
|
expect(
|
61
|
-
Reporting.aggregate(
|
58
|
+
Reporting.aggregate(
|
59
|
+
:sum, :savings, :bonus,
|
60
|
+
:range => TimeRange.make(:year => 2009, :month => 10)
|
61
|
+
),
|
62
62
|
).to eq Money.new(110_00)
|
63
63
|
expect(
|
64
|
-
Reporting.aggregate(
|
64
|
+
Reporting.aggregate(
|
65
|
+
:sum, :savings, :bonus,
|
66
|
+
:range => TimeRange.make(:year => 2009, :month => 11)
|
67
|
+
),
|
65
68
|
).to eq Money.new(90_00)
|
66
69
|
end
|
67
70
|
|
68
71
|
it 'should calculate seperate weeks correctly' do
|
69
72
|
# Week 40 - Mon Sep 28, 2009 to Sun Oct 4 2009
|
70
73
|
expect(
|
71
|
-
Reporting.aggregate(
|
74
|
+
Reporting.aggregate(
|
75
|
+
:sum, :savings, :bonus,
|
76
|
+
:range => TimeRange.make(:year => 2009, :week => 40)
|
77
|
+
),
|
72
78
|
).to eq Money.new(60_00)
|
73
79
|
end
|
74
80
|
|
75
81
|
it 'should calculate seperate days correctly' do
|
76
82
|
# 1 Nov 2009
|
77
83
|
expect(
|
78
|
-
Reporting.aggregate(
|
84
|
+
Reporting.aggregate(
|
85
|
+
:sum, :savings, :bonus,
|
86
|
+
:range => TimeRange.make(:year => 2009, :week => 44, :day => 7)
|
87
|
+
),
|
79
88
|
).to eq Money.new(90_00)
|
80
89
|
end
|
81
90
|
|
82
91
|
it 'should calculate seperate hours correctly' do
|
83
92
|
# 1 Nov 2009
|
84
93
|
expect(
|
85
|
-
Reporting.aggregate(
|
94
|
+
Reporting.aggregate(
|
95
|
+
:sum, :savings, :bonus,
|
96
|
+
:range => TimeRange.make(:year => 2009, :week => 44, :day => 7, :hour => 0)
|
97
|
+
),
|
86
98
|
).to eq Money.new(40_00)
|
87
99
|
expect(
|
88
|
-
Reporting.aggregate(
|
100
|
+
Reporting.aggregate(
|
101
|
+
:sum, :savings, :bonus,
|
102
|
+
:range => TimeRange.make(:year => 2009, :week => 44, :day => 7, :hour => 1)
|
103
|
+
),
|
89
104
|
).to eq Money.new(50_00)
|
90
105
|
end
|
91
106
|
|
92
107
|
it 'should calculate, but not store aggregates when the time range is still current' do
|
93
108
|
Timecop.freeze Time.local(2009, 11, 21) do
|
94
109
|
expect(
|
95
|
-
Reporting.aggregate(
|
110
|
+
Reporting.aggregate(
|
111
|
+
:sum, :savings, :bonus,
|
112
|
+
:range => TimeRange.make(:year => 2009, :month => 11)
|
113
|
+
),
|
96
114
|
).to eq Money.new(90_00)
|
97
115
|
expect(LineAggregate.count).to eq 0
|
98
116
|
end
|
@@ -101,7 +119,10 @@ module DoubleEntry
|
|
101
119
|
it 'should calculate, but not store aggregates when the time range is in the future' do
|
102
120
|
Timecop.freeze Time.local(2009, 11, 21) do
|
103
121
|
expect(
|
104
|
-
Reporting.aggregate(
|
122
|
+
Reporting.aggregate(
|
123
|
+
:sum, :savings, :bonus,
|
124
|
+
:range => TimeRange.make(:year => 2009, :month => 12)
|
125
|
+
),
|
105
126
|
).to eq Money.new(0)
|
106
127
|
expect(LineAggregate.count).to eq 0
|
107
128
|
end
|
@@ -109,51 +130,71 @@ module DoubleEntry
|
|
109
130
|
|
110
131
|
it 'should calculate monthly all_time ranges correctly' do
|
111
132
|
expect(
|
112
|
-
Reporting.aggregate(
|
133
|
+
Reporting.aggregate(
|
134
|
+
:sum, :savings, :bonus,
|
135
|
+
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
136
|
+
),
|
113
137
|
).to eq Money.new(200_00)
|
114
138
|
end
|
115
139
|
|
116
140
|
it 'calculates the average monthly all_time ranges correctly' do
|
117
141
|
expect(
|
118
|
-
Reporting.aggregate(
|
142
|
+
Reporting.aggregate(
|
143
|
+
:average, :savings, :bonus,
|
144
|
+
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
145
|
+
),
|
119
146
|
).to eq expected_monthly_average
|
120
147
|
end
|
121
148
|
|
122
149
|
it 'returns the correct count for weekly all_time ranges correctly' do
|
123
150
|
expect(
|
124
|
-
Reporting.aggregate(
|
151
|
+
Reporting.aggregate(
|
152
|
+
:count, :savings, :bonus,
|
153
|
+
:range => TimeRange.make(:year => 2009, :month => 12, :range_type => :all_time)
|
154
|
+
),
|
125
155
|
).to eq 5
|
126
156
|
end
|
127
157
|
|
128
158
|
it 'should calculate weekly all_time ranges correctly' do
|
129
159
|
expect(
|
130
|
-
Reporting.aggregate(
|
160
|
+
Reporting.aggregate(
|
161
|
+
:sum, :savings, :bonus,
|
162
|
+
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
163
|
+
),
|
131
164
|
).to eq Money.new(110_00)
|
132
165
|
end
|
133
166
|
|
134
167
|
it 'calculates the average weekly all_time ranges correctly' do
|
135
168
|
expect(
|
136
|
-
Reporting.aggregate(
|
169
|
+
Reporting.aggregate(
|
170
|
+
:average, :savings, :bonus,
|
171
|
+
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
172
|
+
),
|
137
173
|
).to eq expected_weekly_average
|
138
174
|
end
|
139
175
|
|
140
176
|
it 'returns the correct count for weekly all_time ranges correctly' do
|
141
177
|
expect(
|
142
|
-
Reporting.aggregate(
|
178
|
+
Reporting.aggregate(
|
179
|
+
:count, :savings, :bonus,
|
180
|
+
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
181
|
+
),
|
143
182
|
).to eq 3
|
144
183
|
end
|
145
184
|
|
146
|
-
it
|
147
|
-
expect
|
148
|
-
Reporting.aggregate(
|
149
|
-
|
185
|
+
it 'raises an AggregateFunctionNotSupported exception' do
|
186
|
+
expect do
|
187
|
+
Reporting.aggregate(
|
188
|
+
:not_supported_calculation, :savings, :bonus,
|
189
|
+
:range => TimeRange.make(:year => 2009, :week => 43, :range_type => :all_time)
|
190
|
+
)
|
191
|
+
end.to raise_error(AggregateFunctionNotSupported)
|
150
192
|
end
|
151
193
|
|
152
194
|
context 'filters' do
|
153
|
-
|
154
195
|
let(:range) { TimeRange.make(:year => 2011, :month => 10) }
|
155
196
|
|
156
|
-
|
197
|
+
DoubleEntry::Line.class_eval do
|
157
198
|
scope :test_filter, -> { where(:amount => 10_00) }
|
158
199
|
end
|
159
200
|
|
@@ -168,42 +209,41 @@ module DoubleEntry
|
|
168
209
|
end
|
169
210
|
|
170
211
|
it 'saves filtered aggregations' do
|
171
|
-
expect
|
212
|
+
expect do
|
172
213
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
173
|
-
|
214
|
+
end.to change { LineAggregate.count }.by 1
|
174
215
|
end
|
175
216
|
|
176
217
|
it 'saves filtered aggregation only once for a range' do
|
177
|
-
expect
|
218
|
+
expect do
|
178
219
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
179
220
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
180
|
-
|
221
|
+
end.to change { LineAggregate.count }.by 1
|
181
222
|
end
|
182
223
|
|
183
224
|
it 'saves filtered aggregations and non filtered aggregations separately' do
|
184
|
-
expect
|
225
|
+
expect do
|
185
226
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
186
227
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range)
|
187
|
-
|
228
|
+
end.to change { LineAggregate.count }.by 2
|
188
229
|
end
|
189
230
|
|
190
231
|
it 'loads the correct saved aggregation' do
|
191
|
-
|
192
232
|
# cache the results for filtered and unfiltered aggregations
|
193
233
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
194
234
|
Reporting.aggregate(:sum, :savings, :bonus, :range => range)
|
195
235
|
|
196
236
|
# ensure a second call loads the correct cached value
|
197
237
|
expect(
|
198
|
-
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter])
|
238
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => range, :filter => [:test_filter]),
|
199
239
|
).to eq Money.new(10_00)
|
200
240
|
expect(
|
201
|
-
Reporting.aggregate(:sum, :savings, :bonus, :range => range)
|
241
|
+
Reporting.aggregate(:sum, :savings, :bonus, :range => range),
|
202
242
|
).to eq Money.new(19_00)
|
203
243
|
end
|
204
244
|
end
|
205
245
|
end
|
206
|
-
describe Aggregate,
|
246
|
+
RSpec.describe Aggregate, 'currencies' do
|
207
247
|
let(:user) { User.make! }
|
208
248
|
before do
|
209
249
|
perform_btc_deposit(user, 100_000_000)
|
@@ -212,8 +252,8 @@ module DoubleEntry
|
|
212
252
|
|
213
253
|
it 'should calculate the sum in the correct currency' do
|
214
254
|
expect(
|
215
|
-
Reporting.aggregate(:sum, :btc_savings, :btc_test_transfer, :range => TimeRange.make(:year => Time.now.year))
|
216
|
-
).to eq
|
255
|
+
Reporting.aggregate(:sum, :btc_savings, :btc_test_transfer, :range => TimeRange.make(:year => Time.now.year)),
|
256
|
+
).to eq(Money.new(300_000_000, :btc))
|
217
257
|
end
|
218
258
|
end
|
219
259
|
end
|