subledger 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE.txt +12 -0
- data/README.md +136 -0
- data/Rakefile +5 -0
- data/bin/subledger +6 -0
- data/lib/subledger/actor.rb +32 -0
- data/lib/subledger/collection_name.rb +25 -0
- data/lib/subledger/domain/account.rb +168 -0
- data/lib/subledger/domain/balance.rb +102 -0
- data/lib/subledger/domain/book.rb +111 -0
- data/lib/subledger/domain/category.rb +157 -0
- data/lib/subledger/domain/control.rb +180 -0
- data/lib/subledger/domain/formatters.rb +31 -0
- data/lib/subledger/domain/identity.rb +159 -0
- data/lib/subledger/domain/journal_entry.rb +293 -0
- data/lib/subledger/domain/key.rb +113 -0
- data/lib/subledger/domain/line.rb +272 -0
- data/lib/subledger/domain/org.rb +110 -0
- data/lib/subledger/domain/report.rb +247 -0
- data/lib/subledger/domain/report_rendering.rb +233 -0
- data/lib/subledger/domain/roles/activatable.rb +11 -0
- data/lib/subledger/domain/roles/archivable.rb +11 -0
- data/lib/subledger/domain/roles/attributable.rb +14 -0
- data/lib/subledger/domain/roles/collectable.rb +175 -0
- data/lib/subledger/domain/roles/creatable.rb +58 -0
- data/lib/subledger/domain/roles/describable.rb +33 -0
- data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
- data/lib/subledger/domain/roles/identifiable.rb +15 -0
- data/lib/subledger/domain/roles/postable.rb +54 -0
- data/lib/subledger/domain/roles/progressable.rb +11 -0
- data/lib/subledger/domain/roles/readable.rb +34 -0
- data/lib/subledger/domain/roles/restable.rb +69 -0
- data/lib/subledger/domain/roles/storable.rb +30 -0
- data/lib/subledger/domain/roles/timeable.rb +18 -0
- data/lib/subledger/domain/roles/updatable.rb +35 -0
- data/lib/subledger/domain/roles/versionable.rb +35 -0
- data/lib/subledger/domain/roles.rb +16 -0
- data/lib/subledger/domain/value/credit.rb +16 -0
- data/lib/subledger/domain/value/debit.rb +16 -0
- data/lib/subledger/domain/value/zero.rb +24 -0
- data/lib/subledger/domain/value.rb +111 -0
- data/lib/subledger/domain.rb +95 -0
- data/lib/subledger/exception_handler.rb +65 -0
- data/lib/subledger/interface/client.rb +295 -0
- data/lib/subledger/interface/dispatcher.rb +20 -0
- data/lib/subledger/interface.rb +2 -0
- data/lib/subledger/path.rb +106 -0
- data/lib/subledger/rest.rb +128 -0
- data/lib/subledger/server.rb +3 -0
- data/lib/subledger/store/api/errors.rb +95 -0
- data/lib/subledger/store/api/roles/activate.rb +21 -0
- data/lib/subledger/store/api/roles/archive.rb +21 -0
- data/lib/subledger/store/api/roles/balance.rb +39 -0
- data/lib/subledger/store/api/roles/categories.rb +51 -0
- data/lib/subledger/store/api/roles/collect.rb +58 -0
- data/lib/subledger/store/api/roles/create.rb +26 -0
- data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
- data/lib/subledger/store/api/roles/create_identity.rb +39 -0
- data/lib/subledger/store/api/roles/create_line.rb +24 -0
- data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
- data/lib/subledger/store/api/roles/post.rb +25 -0
- data/lib/subledger/store/api/roles/progress.rb +21 -0
- data/lib/subledger/store/api/roles/read.rb +19 -0
- data/lib/subledger/store/api/roles/reports.rb +77 -0
- data/lib/subledger/store/api/roles/update.rb +24 -0
- data/lib/subledger/store/api/store.rb +103 -0
- data/lib/subledger/store/api.rb +20 -0
- data/lib/subledger/store.rb +236 -0
- data/lib/subledger/supervisor.rb +21 -0
- data/lib/subledger/uuid.rb +52 -0
- data/lib/subledger/version.rb +4 -0
- data/lib/subledger.rb +234 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/subledger_account_spec.rb +354 -0
- data/spec/subledger_book_spec.rb +130 -0
- data/spec/subledger_category_spec.rb +203 -0
- data/spec/subledger_control_spec.rb +43 -0
- data/spec/subledger_identity_spec.rb +47 -0
- data/spec/subledger_journal_entry_spec.rb +417 -0
- data/spec/subledger_key_spec.rb +43 -0
- data/spec/subledger_org_spec.rb +68 -0
- data/spec/subledger_report_spec.rb +295 -0
- data/spec/subledger_spec.rb +101 -0
- data/subledger.gemspec +52 -0
- metadata +205 -0
data/lib/subledger.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
Encoding.default_internal = Encoding.default_external = 'UTF-8'
|
4
|
+
|
5
|
+
require 'bigdecimal'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
require 'time'
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
|
12
|
+
require 'multi_json'
|
13
|
+
|
14
|
+
require 'subledger/exception_handler'
|
15
|
+
|
16
|
+
module Subledger
|
17
|
+
class Error < StandardError; end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'subledger/collection_name'
|
21
|
+
require 'subledger/domain'
|
22
|
+
require 'subledger/interface'
|
23
|
+
require 'subledger/path'
|
24
|
+
require 'subledger/rest'
|
25
|
+
require 'subledger/uuid'
|
26
|
+
require 'subledger/version'
|
27
|
+
|
28
|
+
# additional requires at bottom
|
29
|
+
|
30
|
+
SUBLEDGER = 'SUBLEDGER'
|
31
|
+
|
32
|
+
module Subledger
|
33
|
+
class DateError < Error; end
|
34
|
+
|
35
|
+
ROOT = File.expand_path __FILE__ + '../../..'
|
36
|
+
|
37
|
+
ROOT_CONFIG = ROOT + '/config'
|
38
|
+
ROOT_LIB = ROOT + '/lib'
|
39
|
+
ROOT_SPEC = ROOT + '/spec'
|
40
|
+
|
41
|
+
LOG = ::Logger.new '/tmp/subledger.log', 10, 102_400_000
|
42
|
+
LOG.sev_threshold = Logger::DEBUG
|
43
|
+
|
44
|
+
case ::ENV['SUBLEDGER_ENV']
|
45
|
+
when nil
|
46
|
+
ENV = 'production'
|
47
|
+
COVER = ::ENV['SUBLEDGER_COVER' ] || false
|
48
|
+
FORMAT = ::ENV['SUBLEDGER_FORMAT'] || 'pride'
|
49
|
+
SCHEME = ::ENV['SUBLEDGER_SCHEME'] || 'https://'
|
50
|
+
DOMAIN = ::ENV['SUBLEDGER_DOMAIN'] || 'api.subledger.com'
|
51
|
+
|
52
|
+
STORE = ( ::ENV['SUBLEDGER_STORE'] || :api ).to_sym
|
53
|
+
AWS_CRED_FILE = nil
|
54
|
+
|
55
|
+
LOG.sev_threshold = Logger::WARN
|
56
|
+
|
57
|
+
when 'test'
|
58
|
+
ENV = 'test'
|
59
|
+
COVER = ::ENV['SUBLEDGER_COVER' ] || false
|
60
|
+
FORMAT = ::ENV['SUBLEDGER_FORMAT'] || 'pride'
|
61
|
+
SCHEME = ::ENV['SUBLEDGER_SCHEME'] || 'http://'
|
62
|
+
DOMAIN = ::ENV['SUBLEDGER_DOMAIN'] || 'api.subledger.dev'
|
63
|
+
|
64
|
+
STORE = ( ::ENV['SUBLEDGER_STORE'] || :memory ).to_sym
|
65
|
+
AWS_CRED_FILE = 'aws_creds_dev.yml'
|
66
|
+
|
67
|
+
when 'test_single'
|
68
|
+
ENV = 'test'
|
69
|
+
COVER = ::ENV['SUBLEDGER_COVER' ] || false
|
70
|
+
FORMAT = ::ENV['SUBLEDGER_FORMAT'] || 'turn'
|
71
|
+
SCHEME = ::ENV['SUBLEDGER_SCHEME'] || 'http://'
|
72
|
+
DOMAIN = ::ENV['SUBLEDGER_DOMAIN'] || 'api.subledger.dev'
|
73
|
+
|
74
|
+
STORE = ( ::ENV['SUBLEDGER_STORE'] || :memory ).to_sym
|
75
|
+
AWS_CRED_FILE = 'aws_creds_dev.yml'
|
76
|
+
|
77
|
+
when 'ci'
|
78
|
+
ENV = 'ci'
|
79
|
+
COVER = ::ENV['SUBLEDGER_COVER' ] || false
|
80
|
+
FORMAT = ::ENV['SUBLEDGER_FORMAT'] || 'normal'
|
81
|
+
SCHEME = ::ENV['SUBLEDGER_SCHEME'] || 'http://'
|
82
|
+
DOMAIN = ::ENV['SUBLEDGER_DOMAIN'] || 'localhost:9292'
|
83
|
+
|
84
|
+
STORE = ( ::ENV['SUBLEDGER_STORE'] || :memory ).to_sym
|
85
|
+
AWS_CRED_FILE = 'aws_creds_dev.yml'
|
86
|
+
|
87
|
+
when 'production'
|
88
|
+
ENV = 'production'
|
89
|
+
COVER = ::ENV['SUBLEDGER_COVER' ] || false
|
90
|
+
FORMAT = ::ENV['SUBLEDGER_FORMAT'] || 'pride'
|
91
|
+
SCHEME = ::ENV['SUBLEDGER_SCHEME'] || 'https://'
|
92
|
+
DOMAIN = ::ENV['SUBLEDGER_DOMAIN'] || 'api.subledger.com'
|
93
|
+
|
94
|
+
STORE = ( ::ENV['SUBLEDGER_STORE'] || :aws ).to_sym
|
95
|
+
AWS_CRED_FILE = 'aws_creds_prod.yml'
|
96
|
+
end
|
97
|
+
|
98
|
+
case STORE
|
99
|
+
when :memory
|
100
|
+
SLOW_POST_DELAY = ::ENV['SUBLEDGER_SLOW_POST_DELAY'] || 0.5
|
101
|
+
PROGRESS_INITIAL = ::ENV['SUBLEDGER_PROGRESS_INITIAL'] || 0.075
|
102
|
+
PROGRESS_RETRY = ::ENV['SUBLEDGER_PROGRESS_RETRY'] || 0.05
|
103
|
+
PROGRESS_FAIL = ::ENV['SUBLEDGER_PROGRESS_FAIL'] || 4
|
104
|
+
BALANCE_SETTLE_DELAY = ::ENV['SUBLEDGER_BALANCE_SETTLE_DELAY'] || 0
|
105
|
+
when :api
|
106
|
+
SLOW_POST_DELAY = ::ENV['SUBLEDGER_SLOW_POST_DELAY'] || 0.5
|
107
|
+
PROGRESS_INITIAL = ::ENV['SUBLEDGER_PROGRESS_INITIAL'] || 0.075
|
108
|
+
PROGRESS_RETRY = ::ENV['SUBLEDGER_PROGRESS_RETRY'] || 0.05
|
109
|
+
PROGRESS_FAIL = ::ENV['SUBLEDGER_PROGRESS_FAIL'] || 4
|
110
|
+
BALANCE_SETTLE_DELAY = ::ENV['SUBLEDGER_BALANCE_SETTLE_DELAY'] || 0
|
111
|
+
when :aws
|
112
|
+
SLOW_POST_DELAY = ::ENV['SUBLEDGER_SLOW_POST_DELAY'] || 10
|
113
|
+
PROGRESS_INITIAL = ::ENV['SUBLEDGER_PROGRESS_INITIAL'] || 1.5
|
114
|
+
PROGRESS_RETRY = ::ENV['SUBLEDGER_PROGRESS_RETRY'] || 0.5
|
115
|
+
PROGRESS_FAIL = ::ENV['SUBLEDGER_PROGRESS_FAIL'] || 20
|
116
|
+
BALANCE_SETTLE_DELAY = ::ENV['SUBLEDGER_BALANCE_SETTLE_DELAY'] || 3
|
117
|
+
end
|
118
|
+
|
119
|
+
unless AWS_CRED_FILE.nil?
|
120
|
+
AWS_PATH = File.expand_path( File.dirname( __FILE__ ) + '/../config/' + AWS_CRED_FILE )
|
121
|
+
end
|
122
|
+
|
123
|
+
RENDERED_REPORTS_BUCKET = "subledger_#{ENV}_rendered_reports"
|
124
|
+
RENDERED_REPORT_CATEGORIES_BUCKET = "subledger_#{ENV}_rendered_report_categories"
|
125
|
+
BACKUP_BUCKET = "subledger_#{ENV}_backups"
|
126
|
+
|
127
|
+
def self.new args = { }
|
128
|
+
args = args.dup
|
129
|
+
|
130
|
+
# auth args
|
131
|
+
|
132
|
+
identity = args[:identity]
|
133
|
+
identity_id = args[:identity_id]
|
134
|
+
|
135
|
+
if identity.nil? and not identity_id.nil?
|
136
|
+
args.merge! :identity => Domain::Identity.
|
137
|
+
new( :id => identity_id )
|
138
|
+
|
139
|
+
args.delete :identity_id
|
140
|
+
end
|
141
|
+
|
142
|
+
key = args[:key]
|
143
|
+
key_id = args[:key_id]
|
144
|
+
|
145
|
+
if key.nil? and not key_id.nil?
|
146
|
+
args.merge! :auth_key => Domain::Key.
|
147
|
+
new( :id => key_id,
|
148
|
+
:identity => args[:identity],
|
149
|
+
:secret => args[:secret] )
|
150
|
+
|
151
|
+
args.delete :key_id
|
152
|
+
args.delete :secret
|
153
|
+
end
|
154
|
+
|
155
|
+
# instance args
|
156
|
+
|
157
|
+
org = args[:org]
|
158
|
+
org_id = args[:org_id]
|
159
|
+
|
160
|
+
if org.nil? and not org_id.nil?
|
161
|
+
args.merge! :org => Domain::Org.
|
162
|
+
new( :id => org_id )
|
163
|
+
|
164
|
+
args.delete :org_id
|
165
|
+
end
|
166
|
+
|
167
|
+
book = args[:book]
|
168
|
+
book_id = args[:book_id]
|
169
|
+
|
170
|
+
if book.nil? and not book_id.nil?
|
171
|
+
args.merge! :book => Domain::Book.
|
172
|
+
new( :id => book_id,
|
173
|
+
:org => args[:org] )
|
174
|
+
|
175
|
+
args.delete :book_id
|
176
|
+
end
|
177
|
+
|
178
|
+
args.merge! :store => store( args )
|
179
|
+
|
180
|
+
Interface::Client.new args
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.store args = { }
|
184
|
+
store_sym = args[:store] || STORE
|
185
|
+
|
186
|
+
begin
|
187
|
+
require "subledger/store/#{store_sym}"
|
188
|
+
rescue LoadError => e
|
189
|
+
raise Error, "Store: #{store_sym}: #{e}"
|
190
|
+
end
|
191
|
+
|
192
|
+
Object.const_get( 'Subledger' ).
|
193
|
+
const_get( 'Store' ).
|
194
|
+
const_get( store_sym.capitalize ).
|
195
|
+
const_get( 'Store' ).new args
|
196
|
+
end
|
197
|
+
|
198
|
+
# Thanks to Jan Goyvaerts, Steven Levithan, O'Reilly Media, Inc.
|
199
|
+
ISO8601_REGEXP = /^(?<year>[0-9]{4})-?(?<month>1[0-2]|0[1-9])-?(?<day>3[0-1]|0[1-9]|[1-2][0-9])T(?<hour>2[0-3]|[0-1][0-9]):?(?<minute>[0-5][0-9]):?(?<second>[0-5][0-9])(?<ms>\.[0-9]{1,3})??(?<timezone>Z|[+-](?:2[0-3]|[0-1][0-9]):?[0-5][0-9])$/
|
200
|
+
# http://my.safaribooksonline.com/book/programming/regular-expressions/9780596802837/4dot-validation-and-formatting/id2983571
|
201
|
+
# Modified to accept optional punctuation and fractional seconds of 1 to 3 digits
|
202
|
+
|
203
|
+
EARLIEST_TIME = Time.utc 1970, 1, 1, 0, 0, 0, 0
|
204
|
+
LATEST_TIME = EARLIEST_TIME + 2 ** 32
|
205
|
+
|
206
|
+
def self.parse_time parameter_name, time_string
|
207
|
+
if time_string.nil?
|
208
|
+
raise DateError, "#{parameter_name} is required"
|
209
|
+
end
|
210
|
+
|
211
|
+
unless time_string.match ISO8601_REGEXP
|
212
|
+
raise DateError, "#{parameter_name} must be ISO 8601 datetime with timezone, optional punctuation, optional fractional seconds (1-3 digits)"
|
213
|
+
end
|
214
|
+
|
215
|
+
time = begin
|
216
|
+
Time.parse( time_string ).utc
|
217
|
+
rescue ArgumentError => e
|
218
|
+
raise DateError, "#{parameter_name}: not a parsable time"
|
219
|
+
end
|
220
|
+
|
221
|
+
unless time >= EARLIEST_TIME and time <= LATEST_TIME
|
222
|
+
raise DateError, "#{parameter_name} must be between #{EARLIEST_TIME.iso8601(3)} and #{LATEST_TIME.iso8601(3)} inclusive"
|
223
|
+
end
|
224
|
+
|
225
|
+
time
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.log_environment
|
229
|
+
LOG.info "SUBLEDGER_STORE: #{STORE}"
|
230
|
+
LOG.info "SUBLEDGER_ENV: #{ENV}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
require 'subledger/store'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
Encoding.default_internal = Encoding.default_external = 'UTF-8'
|
4
|
+
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
require 'subledger'
|
10
|
+
require 'subledger/server'
|
11
|
+
|
12
|
+
require 'minitest/autorun'
|
13
|
+
require 'minitest/mock'
|
14
|
+
require 'minitest/pride' unless Subledger::ENV == 'ci'
|
15
|
+
|
16
|
+
require 'rack/test'
|
17
|
+
|
18
|
+
require Subledger::ROOT_SPEC + '/support/minitest'
|
19
|
+
require Subledger::ROOT_SPEC + '/support/factory'
|
20
|
+
|
21
|
+
module Subledger
|
22
|
+
def self.scenarios
|
23
|
+
[
|
24
|
+
{ :before => ['l', 'k'] },
|
25
|
+
{ :ending => ['m', 'l'] },
|
26
|
+
{ :starting => ['m', 'n'] },
|
27
|
+
{ :after => ['n', 'o'] },
|
28
|
+
{ :preceding => ['l', 'k'] },
|
29
|
+
{ :following => ['n', 'o'] }
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.scenarios_order
|
34
|
+
[
|
35
|
+
{ :before => ['l', 'k'] },
|
36
|
+
{ :ending => ['m', 'l'] },
|
37
|
+
{ :starting => ['m', 'n'] },
|
38
|
+
{ :after => ['n', 'o'] }
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.scenarios_id
|
43
|
+
[
|
44
|
+
{ :preceding => ['l', 'k'] },
|
45
|
+
{ :following => ['n', 'o'] }
|
46
|
+
]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Factory namespace setup
|
51
|
+
|
52
|
+
ActiveBook = Subledger::Domain::ActiveBook
|
53
|
+
ArchivedBook = Subledger::Domain::ArchivedBook
|
54
|
+
ActiveJournalEntry = Subledger::Domain::ActiveJournalEntry
|
55
|
+
PostingJournalEntry = Subledger::Domain::PostingJournalEntry
|
56
|
+
PostedJournalEntry = Subledger::Domain::PostedJournalEntry
|
57
|
+
ArchivedJournalEntry = Subledger::Domain::PostingJournalEntry
|
58
|
+
ActiveAccount = Subledger::Domain::ActiveAccount
|
59
|
+
ArchivedAccount = Subledger::Domain::ArchivedAccount
|
60
|
+
ActiveLine = Subledger::Domain::ActiveLine
|
61
|
+
Debit = Subledger::Domain::Debit
|
62
|
+
Credit = Subledger::Domain::Credit
|
63
|
+
Zero = Subledger::Domain::Zero
|
64
|
+
|
65
|
+
module Kernel
|
66
|
+
private
|
67
|
+
|
68
|
+
def this_method
|
69
|
+
caller[0] =~ /`([^']*)'/ and $1
|
70
|
+
end
|
71
|
+
|
72
|
+
def calling_method
|
73
|
+
caller[1] =~ /`([^']*)'/ and $1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Minitest.autorun
|
@@ -0,0 +1,354 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
Test 'Subledger' do
|
4
|
+
|
5
|
+
let(:factory ) { Subledger::Factory.create }
|
6
|
+
let(:amount ) { '1' }
|
7
|
+
|
8
|
+
balances_order = { :before => [ Subledger::Domain::Debit.new( '3' ),
|
9
|
+
Subledger::Domain::Debit.new( '1' ) ],
|
10
|
+
:ending => [ Subledger::Domain::Debit.new( '6' ),
|
11
|
+
Subledger::Domain::Debit.new( '3' ) ],
|
12
|
+
:starting => [ Subledger::Domain::Debit.new( '6' ),
|
13
|
+
Subledger::Domain::Debit.new( '10' ) ],
|
14
|
+
:after => [ Subledger::Domain::Debit.new( '10' ),
|
15
|
+
Subledger::Domain::Debit.new( '15' ) ] }
|
16
|
+
|
17
|
+
balances_id = { :preceding => [ Subledger::Domain::Debit.new( '3' ),
|
18
|
+
Subledger::Domain::Debit.new( '1' ) ],
|
19
|
+
:following => [ Subledger::Domain::Debit.new( '10' ),
|
20
|
+
Subledger::Domain::Debit.new( '15' ) ] }
|
21
|
+
|
22
|
+
Instance do
|
23
|
+
subject { factory.client }
|
24
|
+
|
25
|
+
Test 'Account' do
|
26
|
+
|
27
|
+
# POST /books/:book_id/accounts
|
28
|
+
RespondsTo 'account.create' do
|
29
|
+
ByReturning 'an instance of Subledger::Domain::ActiveAccount' do
|
30
|
+
subject.account.create( factory.account_args ).
|
31
|
+
must_be_instance_of Subledger::Domain::ActiveAccount
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# GET /books/:book_id/accounts/:id/first_and_last_line
|
36
|
+
[ :archived, :active ].each do |state|
|
37
|
+
RespondsTo 'account.first_and_last_line' do
|
38
|
+
let(:account) { factory.create_account state }
|
39
|
+
|
40
|
+
When 'the account has no lines' do
|
41
|
+
ByReturning "an empty array" do
|
42
|
+
subject.
|
43
|
+
account.
|
44
|
+
first_and_last_line( :id => account.id ).must_be_instance_of Array
|
45
|
+
|
46
|
+
subject.
|
47
|
+
account.
|
48
|
+
first_and_last_line( :id => account.id ).empty?.must_equal true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
When 'the account has lines' do
|
53
|
+
before { factory.create_posted_line_collection account }
|
54
|
+
|
55
|
+
ByReturning "an empty array" do
|
56
|
+
subject.
|
57
|
+
account.
|
58
|
+
first_and_last_line( :id => account.id ).must_be_instance_of Array
|
59
|
+
|
60
|
+
subject.
|
61
|
+
account.
|
62
|
+
first_and_last_line( :id => account.id ).
|
63
|
+
length.must_equal 2
|
64
|
+
|
65
|
+
subject.
|
66
|
+
account.
|
67
|
+
first_and_last_line( :id => account.id ).
|
68
|
+
first.must_be_instance_of Subledger::Domain::PostedLine
|
69
|
+
|
70
|
+
subject.
|
71
|
+
account.
|
72
|
+
first_and_last_line( :id => account.id ).
|
73
|
+
last.must_be_instance_of Subledger::Domain::PostedLine
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
[ :archived, :active ].each do |state|
|
80
|
+
RespondsTo 'account.read when description has a / in it' do
|
81
|
+
let(:description) { 'this has a / in it' }
|
82
|
+
let(:args ) { factory.account_args.
|
83
|
+
merge :description => description }
|
84
|
+
let(:account ) { subject.account.create args }
|
85
|
+
|
86
|
+
ByReturning "an instance of #{state} account" do
|
87
|
+
subject.account.read( :id => account.id ).id.must_equal account.id
|
88
|
+
subject.account.read( :id => account.id ).description.must_equal description
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# PATCH /books/:book_id/accounts/:id
|
94
|
+
[ :archived, :active ].each do |state|
|
95
|
+
RespondsTo 'accounts.update' do
|
96
|
+
let(:account ) { factory.create_account state }
|
97
|
+
let(:args ) { account.attributes }
|
98
|
+
let(:description) { 'updated description' }
|
99
|
+
let(:update_args) { args.merge :description => description,
|
100
|
+
:version => 2 }
|
101
|
+
|
102
|
+
ByReturning "an instance of #{state} account" do
|
103
|
+
patched = subject.accounts.update update_args
|
104
|
+
|
105
|
+
patched.must_be_instance_of account.class
|
106
|
+
patched.description.must_equal description
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# GET /books/:book_id/accounts ? before = :description & limit = :limit & state = :state
|
112
|
+
# ending
|
113
|
+
# starting
|
114
|
+
# after
|
115
|
+
[ :active, :archived ].each do |state|
|
116
|
+
Subledger.scenarios_order.each do |scenario|
|
117
|
+
action = scenario.keys.first
|
118
|
+
expectations = scenario[action]
|
119
|
+
|
120
|
+
RespondsTo "accounts.collect :action => #{action}, :state => #{state}" do
|
121
|
+
let(:accounts) { factory.create_account_collection state }
|
122
|
+
let(:item ) { accounts[2] }
|
123
|
+
let(:limit ) { 2 }
|
124
|
+
let(:klass ) { item.class }
|
125
|
+
|
126
|
+
let(:args) { { :action => action,
|
127
|
+
:description => item.description,
|
128
|
+
:state => state,
|
129
|
+
:limit => limit } }
|
130
|
+
|
131
|
+
When 'not given a block' do
|
132
|
+
ByReturning "a list of #{state} accounts" do
|
133
|
+
subject.accounts.collect( args ).length.must_equal limit
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
When 'given a block' do
|
138
|
+
ByYielding "a list of #{state} accounts" do
|
139
|
+
count = 0
|
140
|
+
|
141
|
+
subject.accounts.collect args do |account|
|
142
|
+
expectation = expectations[count]
|
143
|
+
|
144
|
+
account.description.must_equal expectation
|
145
|
+
|
146
|
+
account.must_be_instance_of klass
|
147
|
+
|
148
|
+
count += 1
|
149
|
+
end
|
150
|
+
|
151
|
+
count.must_equal limit
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# GET /accounts ? preceding = :book_id & limit = :limit & state = :state
|
159
|
+
# following = :book_id & limit = :limit & state = :state
|
160
|
+
[ :active, :archived ].each do |state|
|
161
|
+
Subledger.scenarios_id.each do |scenario|
|
162
|
+
action = scenario.keys.first
|
163
|
+
expectations = scenario[action]
|
164
|
+
|
165
|
+
RespondsTo "accounts.collect :action => #{action}" do
|
166
|
+
let(:accounts) { factory.create_account_collection state }
|
167
|
+
let(:item ) { accounts[2] }
|
168
|
+
let(:limit ) { 2 }
|
169
|
+
let(:klass ) { item.class }
|
170
|
+
|
171
|
+
let(:args) { { :action => action,
|
172
|
+
:id => item.id,
|
173
|
+
:state => state,
|
174
|
+
:limit => limit } }
|
175
|
+
|
176
|
+
When 'not given a block' do
|
177
|
+
ByReturning "a list of #{state} accounts" do
|
178
|
+
subject.accounts.collect( args ).length.must_equal limit
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
When 'given a block' do
|
183
|
+
ByYielding "a list of #{state} accounts" do
|
184
|
+
count = 0
|
185
|
+
|
186
|
+
subject.accounts.collect args do |account|
|
187
|
+
expectation = expectations[count]
|
188
|
+
|
189
|
+
account.description.must_equal expectation
|
190
|
+
|
191
|
+
account.must_be_instance_of klass
|
192
|
+
|
193
|
+
count += 1
|
194
|
+
end
|
195
|
+
|
196
|
+
count.must_equal limit
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# GET /books/:book_id/accounts/:id/lines ? before = :effective_at & limit = :limit
|
204
|
+
# ending
|
205
|
+
# starting
|
206
|
+
# after
|
207
|
+
Subledger.scenarios_order.each do |scenario|
|
208
|
+
action = scenario.keys.first
|
209
|
+
expectations = scenario[action]
|
210
|
+
balances = balances_order[action]
|
211
|
+
|
212
|
+
RespondsTo "account.read(:id => id).lines action => #{action}" do
|
213
|
+
let(:account) { factory.create_account :active }
|
214
|
+
let(:lines ) { factory.create_posted_line_collection account }
|
215
|
+
let(:item ) { lines[2] }
|
216
|
+
let(:limit ) { 2 }
|
217
|
+
|
218
|
+
let(:args) { { :action => action,
|
219
|
+
:effective_at => item.effective_at,
|
220
|
+
:limit => limit } }
|
221
|
+
|
222
|
+
When 'not given a block' do
|
223
|
+
ByReturning 'a list of account lines' do
|
224
|
+
account.lines( args ).length.must_equal expectations.length
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
When 'given a block' do
|
229
|
+
ByYielding 'a list of account lines' do
|
230
|
+
count = 0
|
231
|
+
|
232
|
+
account.lines args do |line|
|
233
|
+
expectation = expectations[count]
|
234
|
+
line.description.must_equal expectation
|
235
|
+
|
236
|
+
line.must_be_instance_of Subledger::Domain::PostedLine
|
237
|
+
|
238
|
+
line.posted_at.wont_be_nil
|
239
|
+
line.effective_at.wont_be_nil
|
240
|
+
|
241
|
+
line.balance.must_be_instance_of Subledger::Domain::Balance
|
242
|
+
|
243
|
+
line.balance.must_equal balances[count]
|
244
|
+
|
245
|
+
count += 1
|
246
|
+
end
|
247
|
+
|
248
|
+
count.must_equal expectations.length
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# GET /books/:book_id/accounts/:id/lines ? preceding = :line_id & limit = :limit
|
255
|
+
# following = :line_id & limit = :limit
|
256
|
+
Subledger.scenarios_id.each do |scenario|
|
257
|
+
action = scenario.keys.first
|
258
|
+
expectations = scenario[action]
|
259
|
+
balances = balances_id[action]
|
260
|
+
|
261
|
+
RespondsTo "account_instance.lines action => #{action}" do
|
262
|
+
let(:account) { factory.create_account :active }
|
263
|
+
let(:lines ) { factory.create_posted_line_collection account }
|
264
|
+
let(:item ) { lines[2] }
|
265
|
+
let(:limit ) { 2 }
|
266
|
+
|
267
|
+
let(:args) { { :action => action,
|
268
|
+
:id => item.id,
|
269
|
+
:limit => limit } }
|
270
|
+
|
271
|
+
When 'not given a block' do
|
272
|
+
ByReturning 'a list of account lines' do
|
273
|
+
account.lines( args ).length.must_equal expectations.length
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
When 'given a block' do
|
278
|
+
ByYielding 'a list of account lines' do
|
279
|
+
count = 0
|
280
|
+
|
281
|
+
account.lines args do |line|
|
282
|
+
expectation = expectations[count]
|
283
|
+
|
284
|
+
line.description.must_equal expectation
|
285
|
+
|
286
|
+
line.must_be_instance_of Subledger::Domain::PostedLine
|
287
|
+
|
288
|
+
line.posted_at.wont_be_nil
|
289
|
+
line.effective_at.wont_be_nil
|
290
|
+
|
291
|
+
line.balance.must_be_instance_of Subledger::Domain::Balance
|
292
|
+
|
293
|
+
line.balance.must_equal balances[count]
|
294
|
+
|
295
|
+
count += 1
|
296
|
+
end
|
297
|
+
|
298
|
+
count.must_equal expectations.length
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# GET /books/:book_id/accounts/:account_id/lines/:id
|
305
|
+
|
306
|
+
RespondsTo 'account.line.read' do
|
307
|
+
let(:account) { factory.create_account :active }
|
308
|
+
let(:lines ) { factory.create_posted_line_collection account }
|
309
|
+
let(:line ) { lines[2] }
|
310
|
+
|
311
|
+
ByReturning 'Subledger::Domain::PostedLine' do
|
312
|
+
posted_line = account.line( :id => line.id ).read
|
313
|
+
posted_line.must_be_instance_of Subledger::Domain::PostedLine
|
314
|
+
posted_line.posted_at.must_be_instance_of Time
|
315
|
+
posted_line.effective_at.must_be_instance_of Time
|
316
|
+
|
317
|
+
if Subledger::STORE == :api
|
318
|
+
posted_line.balance.must_be_instance_of Subledger::Domain::Balance
|
319
|
+
posted_line.balance.must_equal factory.
|
320
|
+
client.
|
321
|
+
debit( '6' )
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# POST /books/:book_id/accounts/:id/archive
|
327
|
+
RespondsTo 'account_instance.archive' do
|
328
|
+
ByReturning 'an instance of Subledger::Domain::ArchivedAccount' do
|
329
|
+
factory.create_account(:active).archive.
|
330
|
+
must_be_instance_of Subledger::Domain::ArchivedAccount
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# POST /books/:book_id/archived_accounts/:id/activate
|
335
|
+
RespondsTo 'archived_account_instance.activate' do
|
336
|
+
ByReturning 'an instance of Subledger::Domain::ActiveAccount' do
|
337
|
+
factory.create_account(:archived).activate.
|
338
|
+
must_be_instance_of Subledger::Domain::ActiveAccount
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# GET /books/:book_id/accounts/:id/balance/:at
|
343
|
+
[ :archived, :active ].each do |state|
|
344
|
+
RespondsTo 'account_instance.balance' do
|
345
|
+
let(:account) { factory.create_account state }
|
346
|
+
|
347
|
+
ByReturning 'a Subledger::Domain::Balance' do
|
348
|
+
account.balance( :at => Time.now ).must_be_instance_of Subledger::Domain::Balance
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|