subledger 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE.txt +12 -0
  3. data/README.md +136 -0
  4. data/Rakefile +5 -0
  5. data/bin/subledger +6 -0
  6. data/lib/subledger/actor.rb +32 -0
  7. data/lib/subledger/collection_name.rb +25 -0
  8. data/lib/subledger/domain/account.rb +168 -0
  9. data/lib/subledger/domain/balance.rb +102 -0
  10. data/lib/subledger/domain/book.rb +111 -0
  11. data/lib/subledger/domain/category.rb +157 -0
  12. data/lib/subledger/domain/control.rb +180 -0
  13. data/lib/subledger/domain/formatters.rb +31 -0
  14. data/lib/subledger/domain/identity.rb +159 -0
  15. data/lib/subledger/domain/journal_entry.rb +293 -0
  16. data/lib/subledger/domain/key.rb +113 -0
  17. data/lib/subledger/domain/line.rb +272 -0
  18. data/lib/subledger/domain/org.rb +110 -0
  19. data/lib/subledger/domain/report.rb +247 -0
  20. data/lib/subledger/domain/report_rendering.rb +233 -0
  21. data/lib/subledger/domain/roles/activatable.rb +11 -0
  22. data/lib/subledger/domain/roles/archivable.rb +11 -0
  23. data/lib/subledger/domain/roles/attributable.rb +14 -0
  24. data/lib/subledger/domain/roles/collectable.rb +175 -0
  25. data/lib/subledger/domain/roles/creatable.rb +58 -0
  26. data/lib/subledger/domain/roles/describable.rb +33 -0
  27. data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
  28. data/lib/subledger/domain/roles/identifiable.rb +15 -0
  29. data/lib/subledger/domain/roles/postable.rb +54 -0
  30. data/lib/subledger/domain/roles/progressable.rb +11 -0
  31. data/lib/subledger/domain/roles/readable.rb +34 -0
  32. data/lib/subledger/domain/roles/restable.rb +69 -0
  33. data/lib/subledger/domain/roles/storable.rb +30 -0
  34. data/lib/subledger/domain/roles/timeable.rb +18 -0
  35. data/lib/subledger/domain/roles/updatable.rb +35 -0
  36. data/lib/subledger/domain/roles/versionable.rb +35 -0
  37. data/lib/subledger/domain/roles.rb +16 -0
  38. data/lib/subledger/domain/value/credit.rb +16 -0
  39. data/lib/subledger/domain/value/debit.rb +16 -0
  40. data/lib/subledger/domain/value/zero.rb +24 -0
  41. data/lib/subledger/domain/value.rb +111 -0
  42. data/lib/subledger/domain.rb +95 -0
  43. data/lib/subledger/exception_handler.rb +65 -0
  44. data/lib/subledger/interface/client.rb +295 -0
  45. data/lib/subledger/interface/dispatcher.rb +20 -0
  46. data/lib/subledger/interface.rb +2 -0
  47. data/lib/subledger/path.rb +106 -0
  48. data/lib/subledger/rest.rb +128 -0
  49. data/lib/subledger/server.rb +3 -0
  50. data/lib/subledger/store/api/errors.rb +95 -0
  51. data/lib/subledger/store/api/roles/activate.rb +21 -0
  52. data/lib/subledger/store/api/roles/archive.rb +21 -0
  53. data/lib/subledger/store/api/roles/balance.rb +39 -0
  54. data/lib/subledger/store/api/roles/categories.rb +51 -0
  55. data/lib/subledger/store/api/roles/collect.rb +58 -0
  56. data/lib/subledger/store/api/roles/create.rb +26 -0
  57. data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
  58. data/lib/subledger/store/api/roles/create_identity.rb +39 -0
  59. data/lib/subledger/store/api/roles/create_line.rb +24 -0
  60. data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
  61. data/lib/subledger/store/api/roles/post.rb +25 -0
  62. data/lib/subledger/store/api/roles/progress.rb +21 -0
  63. data/lib/subledger/store/api/roles/read.rb +19 -0
  64. data/lib/subledger/store/api/roles/reports.rb +77 -0
  65. data/lib/subledger/store/api/roles/update.rb +24 -0
  66. data/lib/subledger/store/api/store.rb +103 -0
  67. data/lib/subledger/store/api.rb +20 -0
  68. data/lib/subledger/store.rb +236 -0
  69. data/lib/subledger/supervisor.rb +21 -0
  70. data/lib/subledger/uuid.rb +52 -0
  71. data/lib/subledger/version.rb +4 -0
  72. data/lib/subledger.rb +234 -0
  73. data/spec/spec_helper.rb +77 -0
  74. data/spec/subledger_account_spec.rb +354 -0
  75. data/spec/subledger_book_spec.rb +130 -0
  76. data/spec/subledger_category_spec.rb +203 -0
  77. data/spec/subledger_control_spec.rb +43 -0
  78. data/spec/subledger_identity_spec.rb +47 -0
  79. data/spec/subledger_journal_entry_spec.rb +417 -0
  80. data/spec/subledger_key_spec.rb +43 -0
  81. data/spec/subledger_org_spec.rb +68 -0
  82. data/spec/subledger_report_spec.rb +295 -0
  83. data/spec/subledger_spec.rb +101 -0
  84. data/subledger.gemspec +52 -0
  85. 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'
@@ -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