subledger 0.7.7
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.
- 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
|