freeagent_api 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,4 +2,4 @@
2
2
  .DS_Store
3
3
  coverage
4
4
  rdoc
5
- pkg
5
+ pkg
data/README.rdoc CHANGED
@@ -1,17 +1,24 @@
1
1
  = freeagent_api
2
2
 
3
- Simple Ruby interface to the Freeagent Central API (http://www.freeagentcentral.com/developers/freeagent-api).
3
+ Simple ActiveResource Ruby wrapper for the Freeagent Central API (http://www.freeagentcentral.com/developers/freeagent-api).
4
4
 
5
- This is an early development version of a Ruby wrapper for the Freeagent API. Currently this only supports GET requests (POST will follow shortly) and only the following API methods are supported (more will follow):
5
+ This supports all GET, POST, PUT and DELETE ActiveResource calls for the following API resources:
6
6
 
7
+ * Company
7
8
  * Contacts
8
- * Invoices
9
- * Invoice items
10
9
  * Projects
11
10
  * Tasks
11
+ * Invoices
12
+ * Invoice Items
12
13
  * Timeslips
13
14
 
14
- There is no test suite yet. If you feel brave, then feel free to clone, fork and play around.
15
+ At the moment, the following API resources are NOT supported (although is being worked on):
16
+
17
+ * Users
18
+ * Expenses
19
+ * Attachments
20
+
21
+ Feel free to clone, fork and add tests.
15
22
 
16
23
  == Installation
17
24
 
@@ -19,43 +26,208 @@ To install as a Gem, just run:
19
26
 
20
27
  $ sudo gem install freeagent_api -s http://gemcutter.org
21
28
 
29
+ Please note: version 0.2 is significantly different from 0.1 so if you are upgrading from the early development version please re-familiarise yourself with the documentation.
30
+
22
31
  == Usage
23
32
 
24
33
  === Authentication
25
34
 
26
- Freeagent.domain = 'yourdomain.freeagentcentral.com'
27
- Freeagent.username = 'your@login.com'
28
- Freeagent.password = 'your_password'
35
+ Freeagent.authenticate({
36
+ :domain => 'yourdomain.freeagentcentral.com',
37
+ :username => 'your@login.com',
38
+ :password => 'your_password'})
39
+
40
+ === Company
41
+
42
+ <b>Timelines</b>
43
+
44
+ @invoice_timeline = Company.invoice_timeline
45
+ @tax_timeline = Company.tax_timeline
29
46
 
30
47
  === Contacts
31
48
 
32
- @contacts = Contact.find_all # returns all contacts
33
- @contact = Contact.find(contact_id) # returns specific contact
49
+ <b>Find contacts</b>
34
50
 
35
- === Invoice
51
+ @contacts = Contact.find :all # returns all contacts
52
+ @contact = Contact.find id # returns specific contact
36
53
 
37
- @invoices = Invoice.find_all # returns all invoices
38
- @invoices = Invoice.find_all(project_id) # returns all invoices for project
39
- @invoice = Invoice.find(invoice_id) # returns specific invoice
54
+ <b>Create contact</b>
40
55
 
41
- === Invoice items
56
+ # Required attributes
57
+ # :first_name
58
+ # :last_name
59
+
60
+ @contact = Contact.new params
61
+ @contact.save
62
+
63
+ <b>Update contact</b>
64
+
65
+ @contact.first_name = 'Joe'
66
+ @contact.save
42
67
 
43
- @items = InvoiceItem.find_all(invoice_id) # returns all items for invoice
68
+ <b>Delete contact</b>
69
+
70
+ Contact.delete id
71
+ # or
72
+ @contact.destroy
44
73
 
45
74
  === Projects
46
75
 
47
- @projects = Project.find_all # returns all projects
48
- @project = Project.find(project_id) # returns specific project
76
+ <b>Find projects</b>
77
+
78
+ @projects = Project.find :all # returns all projects
79
+ @project = Project.find id # returns specific project
80
+
81
+ <b>Create project</b>
82
+
83
+ # Required attribues
84
+ # :contact_id
85
+ # :name
86
+ # :payment_term_in_days
87
+ # :billing_basis # must be 1, 7, 7.5, or 8
88
+ # :budget_units # must be Hours, Days, or Monetary
89
+ # :status # must be Active or Completed
90
+
91
+ @project = Project.new params
92
+ @project.save
93
+
94
+ <b>Update project</b>
95
+
96
+ @project.name = 'Web design project'
97
+ @project.save
98
+
99
+ <b>Delete project</b>
100
+
101
+ Project.delete id
102
+ # or
103
+ @project.destroy
104
+
105
+ <b>Nested resources</b>
106
+
107
+ @invoices = @project.invoices
108
+ @timeslips = @project.timeslips
49
109
 
50
110
  === Tasks
51
111
 
52
- @task = Task.find(project_id, task_id) # returns specific task for project
112
+ <b>Find tasks</b>
113
+
114
+ @tasks = Task.find :all # returns all tasks
115
+ @task = Task.find id # returns specific task
116
+
117
+ <b>Create task</b>
118
+
119
+ # Required attributes
120
+ # :name
121
+
122
+ @task = Task.new params
123
+ @task.save
124
+
125
+ <b>Update task</b>
126
+
127
+ @task.name = 'Create wireframes'
128
+ @task.save
129
+
130
+ <b>Delete task</b>
131
+
132
+ Task.delete id
133
+ # or
134
+ @task.destroy
135
+
136
+ === Invoices
137
+
138
+ <b>Find Invoices</b>
139
+
140
+ @invoices = Invoice.find :all # returns all invoices
141
+ @invoice = Invoice.find id # returns specific invoice
142
+
143
+ <b>Create invoice</b>
144
+
145
+ # Required attributes
146
+ # :contact_id
147
+ # :project_id
148
+ # :dated_on
149
+ # :reference
150
+ # :status
151
+
152
+ @invoice = Invoice.new params
153
+ @invoice.save
154
+
155
+ <b>Update invoice</b>
156
+
157
+ @invoice.status = 'Sent'
158
+ @invoice.save
159
+
160
+ <b>Delete invoice</b>
161
+
162
+ Invoice.delete id
163
+ # or
164
+ @invoice.destroy
165
+
166
+ <b>Changing status</b>
167
+
168
+ @invoice.mark_as_draft
169
+ @invoice.mark_as_sent
170
+ @invoice.mark_as_cancelled
171
+
172
+ === Invoice items
173
+
174
+ <b>Find invoice items</b>
175
+
176
+ @invoice_items = InvoiceItem.find :all # returns all invoice items
177
+ @invoice_item = InvoiceItem.find id # returns specific invoice item
178
+
179
+ <b>Create invoice item</b>
180
+
181
+ # Required attributes
182
+ # :item_type # must be Hours, Days, Months, Years, Products, Services, Expenses, Discount, Credit, Comment
183
+ # :description
184
+ # :quantity
185
+ # :price
186
+ # :sales_tax_rate
187
+
188
+ @invoice_item = InvoiceItem.new params
189
+ @invoice_item.save
190
+
191
+ <b>Update invoice item</b>
192
+
193
+ @invoice_item.name = 'Create wireframes'
194
+ @invoice_item.save
195
+
196
+ <b>Delete invoice item</b>
197
+
198
+ InvoiceItem.delete id
199
+ # or
200
+ @invoice_item.destroy
53
201
 
54
202
  === Timeslips
55
203
 
56
- @timeslips = Timeslip.find_all # returns all timeslips
57
- @timeslips = Timeslip.find_all(project_id) # returns all timeslips for project
58
- @timeslip = Timeslip.find(timeslip_id) # returns specific timeslip
204
+ <b>Find timeslips</b>
205
+
206
+ @timeslips = Timeslip.find :all, :params => {:from => '2009-10-01', :to => '2009-10-30'}
207
+ # returns all timeslips (:from and :to dates required)
208
+ @timeslip = Timeslip.find id # returns specific timeslip
209
+
210
+ <b>Create timeslip</b>
211
+
212
+ # Required attributes
213
+ # :user_id
214
+ # :hours
215
+ # :dated_on
216
+ # :task_id OR :new_task
217
+
218
+ @timeslip = Timeslip.new params
219
+ @timeslip.save
220
+
221
+ <b>Update timeslip</b>
222
+
223
+ @timeslip.hours = '3.5'
224
+ @timeslip.save
225
+
226
+ <b>Delete timeslip</b>
227
+
228
+ Timeslip.delete id
229
+ # or
230
+ @timeslip.destroy
59
231
 
60
232
  == Author
61
233
 
data/Rakefile CHANGED
@@ -5,15 +5,14 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "freeagent_api"
8
- gem.summary = %Q{Simple Ruby interface to the Freeagent Central API.}
9
- gem.description = %Q{This is an early development version of a Ruby wrapper for the Freeagent API. Currently this only supports GET requests (POST will follow shortly) and not all API methods are currently supported (more will follow).}
8
+ gem.summary = %Q{ActiveResource Ruby wrapper for the Freeagent Central API.}
9
+ gem.description = %Q{This is an ActiveResource Ruby wrapper for the Freeagent API. Currently supports the following API resources: Company, Contacts, Projects, Tasks, Invoices, Invoice Items, Timeslips (more will follow).}
10
10
  gem.email = "aaron@gc4.co.uk"
11
11
  gem.homepage = "http://github.com/aaronrussell/freeagent_api"
12
12
  gem.authors = ["Aaron Russell"]
13
13
  gem.add_development_dependency "thoughtbot-shoulda"
14
- gem.add_dependency "activesupport"
15
- gem.add_dependency "nokogiri"
16
- gem.add_dependency "api_cache"
14
+ gem.add_development_dependency "fakeweb"
15
+ gem.add_dependency "activeresource"
17
16
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
17
  end
19
18
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{freeagent_api}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Aaron Russell"]
12
- s.date = %q{2009-10-14}
13
- s.description = %q{This is an early development version of a Ruby wrapper for the Freeagent API. Currently this only supports GET requests (POST will follow shortly) and not all API methods are currently supported (more will follow).}
12
+ s.date = %q{2009-10-27}
13
+ s.description = %q{This is an ActiveResource Ruby wrapper for the Freeagent API. Currently supports the following API resources: Company, Contacts, Projects, Tasks, Invoices, Invoice Items, Timeslips (more will follow).}
14
14
  s.email = %q{aaron@gc4.co.uk}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE",
@@ -25,17 +25,49 @@ Gem::Specification.new do |s|
25
25
  "VERSION",
26
26
  "freeagent_api.gemspec",
27
27
  "lib/freeagent_api.rb",
28
- "test/freeagent_api_test.rb",
29
- "test/test_helper.rb"
28
+ "test/authentication_test.rb",
29
+ "test/company_test.rb",
30
+ "test/contact_test.rb",
31
+ "test/invoice_item_test.rb",
32
+ "test/invoice_test.rb",
33
+ "test/project_test.rb",
34
+ "test/stubs/company/invoice_timeline",
35
+ "test/stubs/company/tax_timeline",
36
+ "test/stubs/contacts/find_all",
37
+ "test/stubs/contacts/find_single",
38
+ "test/stubs/http/200",
39
+ "test/stubs/http/201",
40
+ "test/stubs/invoice_items/find_all",
41
+ "test/stubs/invoice_items/find_single",
42
+ "test/stubs/invoices/find_all",
43
+ "test/stubs/invoices/find_single",
44
+ "test/stubs/projects/find_all",
45
+ "test/stubs/projects/find_single",
46
+ "test/stubs/projects/invoices",
47
+ "test/stubs/projects/timeslips",
48
+ "test/stubs/tasks/find_all",
49
+ "test/stubs/tasks/find_single",
50
+ "test/stubs/timeslips/find_all",
51
+ "test/stubs/timeslips/find_single",
52
+ "test/task_test.rb",
53
+ "test/test_helper.rb",
54
+ "test/timeslip_test.rb"
30
55
  ]
31
56
  s.homepage = %q{http://github.com/aaronrussell/freeagent_api}
32
57
  s.rdoc_options = ["--charset=UTF-8"]
33
58
  s.require_paths = ["lib"]
34
59
  s.rubygems_version = %q{1.3.5}
35
- s.summary = %q{Simple Ruby interface to the Freeagent Central API.}
60
+ s.summary = %q{ActiveResource Ruby wrapper for the Freeagent Central API.}
36
61
  s.test_files = [
37
- "test/freeagent_api_test.rb",
38
- "test/test_helper.rb"
62
+ "test/authentication_test.rb",
63
+ "test/company_test.rb",
64
+ "test/contact_test.rb",
65
+ "test/invoice_item_test.rb",
66
+ "test/invoice_test.rb",
67
+ "test/project_test.rb",
68
+ "test/task_test.rb",
69
+ "test/test_helper.rb",
70
+ "test/timeslip_test.rb"
39
71
  ]
40
72
 
41
73
  if s.respond_to? :specification_version then
@@ -44,19 +76,16 @@ Gem::Specification.new do |s|
44
76
 
45
77
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
78
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
47
- s.add_runtime_dependency(%q<activesupport>, [">= 0"])
48
- s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
49
- s.add_runtime_dependency(%q<api_cache>, [">= 0"])
79
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
80
+ s.add_runtime_dependency(%q<activeresource>, [">= 0"])
50
81
  else
51
82
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
- s.add_dependency(%q<activesupport>, [">= 0"])
53
- s.add_dependency(%q<nokogiri>, [">= 0"])
54
- s.add_dependency(%q<api_cache>, [">= 0"])
83
+ s.add_dependency(%q<fakeweb>, [">= 0"])
84
+ s.add_dependency(%q<activeresource>, [">= 0"])
55
85
  end
56
86
  else
57
87
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
58
- s.add_dependency(%q<activesupport>, [">= 0"])
59
- s.add_dependency(%q<nokogiri>, [">= 0"])
60
- s.add_dependency(%q<api_cache>, [">= 0"])
88
+ s.add_dependency(%q<fakeweb>, [">= 0"])
89
+ s.add_dependency(%q<activeresource>, [">= 0"])
61
90
  end
62
91
  end
data/lib/freeagent_api.rb CHANGED
@@ -1,172 +1,115 @@
1
1
  require 'rubygems'
2
- require 'nokogiri'
3
- require 'net/https'
4
- require 'api_cache'
5
- require 'activesupport'
2
+ require 'activeresource'
6
3
 
7
4
  module Freeagent
8
5
 
9
6
  class << self
10
- attr_accessor :domain, :username, :password
11
-
12
- def self.domain=(domain)
13
- @domain = domain
14
- end
15
-
16
- def self.username=(username)
17
- @username = username
7
+ def authenticate(options)
8
+ Base.authenticate(options)
18
9
  end
10
+ end
11
+
12
+ class Error < StandardError; end
19
13
 
20
- def self.password=(password)
21
- @password = password
14
+ class Base < ActiveResource::Base
15
+ def self.authenticate(options)
16
+ self.site = "https://#{options[:domain]}"
17
+ self.user = options[:username]
18
+ self.password = options[:password]
22
19
  end
23
20
  end
24
21
 
25
- class Base
26
-
27
- def initialize(attributes={})
28
- attributes.each do |key, value|
29
- raise "no attr_accessor set for #{key} on #{self.class}" if !respond_to?("#{key}=")
30
- self.send("#{key}=", value)
31
- end
32
- end
22
+ # Company
33
23
 
34
- def self.get(path)
35
- @@resp = APICache.get(path) do
36
- http = Net::HTTP.new(Freeagent.domain, 443)
37
- http.use_ssl = true
38
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
- http.start do |http|
40
- request = Net::HTTP::Get.new(path, {'Content-Type' => 'application/xml', 'Accept' => 'application/xml'})
41
- request.basic_auth(Freeagent.username, Freeagent.password)
42
- response = http.request(request)
43
- case response
44
- when Net::HTTPSuccess
45
- @@resp = response.body
46
- else
47
- response.error!
48
- raise APICache::InvalidResponse
49
- end
50
- end
51
- end
24
+ class Company
25
+ def self.invoice_timeline
26
+ InvoiceTimeline.find :all, :from => '/company/invoice_timeline.xml'
52
27
  end
53
-
54
- def self.parse(path, options)
55
- response = []
56
- Nokogiri::XML(@@resp).xpath(path).each do |ts|
57
- res = {}
58
- options.each do |key|
59
- res[key.underscore.to_sym] = ts.xpath(key).text
60
- end
61
- response << self.new(res)
62
- end
63
- return response
28
+ def self.tax_timeline
29
+ TaxTimeline.find :all, :from => '/company/tax_timeline.xml'
64
30
  end
65
-
31
+ end
32
+ class InvoiceTimeline < Base
33
+ self.prefix = '/company/'
34
+ end
35
+ class TaxTimeline < Base
36
+ self.prefix = '/company/'
66
37
  end
67
38
 
39
+ # Contacts
40
+
68
41
  class Contact < Base
69
- @elements = ['id', 'organisation-name', 'first-name', 'last-name', 'address1', 'address2', 'address3', 'town', 'region', 'country', 'postcode', 'phone-number', 'email', 'contact-name-on-invoices', 'sales-tax-registration_number', 'uses-contact-invoice-sequence']
70
- @elements.each {|t| attr_accessor t.underscore.to_sym}
71
-
72
- def self.find_all
73
- get '/contacts'
74
- contacts = parse('contacts/contact', @elements)
75
- return contacts
42
+ end
43
+
44
+ # Projects
45
+
46
+ class Project < Base
47
+
48
+ def invoices
49
+ Invoice.find :all, :from => "/projects/#{id}/invoices.xml"
76
50
  end
77
-
78
- def self.find(contact_id)
79
- get '/contacts/'+contact_id
80
- contacts = parse('contact', @elements)
81
- return contacts[0]
51
+
52
+ def timeslips
53
+ Timeslip.find :all, :from => "/projects/#{id}/timeslips.xml"
82
54
  end
55
+
56
+ end
57
+
58
+ # Tasks - Complete
59
+
60
+ class Task < Base
61
+ self.prefix = '/projects/:project_id/'
83
62
  end
84
63
 
64
+ # Invoices - Complete
65
+
85
66
  class Invoice < Base
86
- @elements = ['id', 'contact-id', 'project-id', 'dated-on', 'due-on', 'reference', 'net-value', 'sales-tax-value', 'status', 'comments', 'discount-percent', 'omit-header', 'payment-terms', 'written-off-date', 'invoice-items']
87
- @elements.each {|t| attr_accessor t.underscore.to_sym}
88
67
 
89
- def self.find_all(project_id = false)
90
- if project_id
91
- get '/projects/'+project_id+'/invoices'
92
- else
93
- get '/invoices'
68
+ def mark_as_draft
69
+ connection.put("/invoices/#{id}/mark_as_draft.xml", encode, self.class.headers).tap do |response|
70
+ load_attributes_from_response(response)
94
71
  end
95
- invoices = parse('invoices/invoice', @elements)
96
- return invoices.reverse
97
72
  end
98
-
99
- def self.find(invoice_id)
100
- get '/invoices/'+invoice_id
101
- invoices = parse('invoice', @elements)
102
- invoices.each do |i|
103
- i.invoice_items = InvoiceItem.find_all(invoice_id)
73
+ def mark_as_sent
74
+ connection.put("/invoices/#{id}/mark_as_sent.xml", encode, self.class.headers).tap do |response|
75
+ load_attributes_from_response(response)
104
76
  end
105
- return invoices[0]
106
77
  end
107
- end
108
-
109
- class InvoiceItem < Invoice
110
- @elements = ['id', 'invoice-id', 'project-id', 'item-type', 'price', 'quantity', 'description', 'sales-tax-rate']
111
- @elements.each {|t| attr_accessor t.underscore.to_sym}
112
-
113
- def self.find_all(invoice_id)
114
- items = parse('invoice/invoice-items/invoice-item', @elements)
115
- return items
116
- end
117
- end
118
-
119
- class Project < Base
120
- @elements = ['id', 'contact-id', 'name', 'billing-basis', 'budget', 'budget-units', 'invoicing-reference', 'is-ir35', 'normal-billing-rate', 'payment-terms-in-days', 'starts-on', 'ends-on', 'status', 'uses-project-invoice-sequence']
121
- @elements.each {|t| attr_accessor t.underscore.to_sym}
122
-
123
- def self.find_all
124
- get '/projects'
125
- projects = parse('projects/project', @elements)
126
- return projects
78
+ def mark_as_cancelled
79
+ connection.put("/invoices/#{id}/mark_as_cancelled.xml", encode, self.class.headers).tap do |response|
80
+ load_attributes_from_response(response)
81
+ end
127
82
  end
128
83
 
129
- def self.find(project_id)
130
- get '/projects/'+project_id
131
- projects = parse('project', @elements)
132
- return projects[0]
133
- end
134
84
  end
135
85
 
136
- class Task < Base
137
- @elements = ['id', 'project-id', 'name']
138
- @elements.each {|t| attr_accessor t.underscore.to_sym}
139
-
140
- def self.find(project_id, task_id)
141
- get '/projects/'+project_id+'/tasks/'+task_id
142
- tasks = parse('task', @elements)
143
- return tasks[0]
144
- end
86
+ # Invoice items - Complete
87
+
88
+ class InvoiceItem < Base
89
+ self.prefix = '/invoices/:invoice_id/'
145
90
  end
91
+
92
+ # Timeslips
146
93
 
147
94
  class Timeslip < Base
148
- @elements = ['id', 'dated-on', 'project-id', 'task-id', 'task', 'user-id', 'hours', 'comment']
149
- @elements.each {|t| attr_accessor t.underscore.to_sym}
150
-
151
- def self.find_all(project_id = false)
152
- if project_id
153
- get '/projects/'+project_id+'/timeslips'
154
- else
155
- get '/timeslips'
95
+
96
+ def self.find(*arguments)
97
+ scope = arguments.slice!(0)
98
+ options = arguments.slice!(0) || {}
99
+ if options[:params] && options[:params][:from] && options[:params][:to]
100
+ options[:params][:view] = options[:params][:from]+'_'+options[:params][:to]
101
+ options[:params].delete(:from)
102
+ options[:params].delete(:to)
156
103
  end
157
- timeslips = parse('timeslips/timeslip', @elements)
158
- timeslips.each do |t|
159
- t.task = Task.find(project_id, t.task_id)
104
+
105
+ case scope
106
+ when :all then find_every(options)
107
+ when :first then find_every(options).first
108
+ when :last then find_every(options).last
109
+ when :one then find_one(options)
110
+ else find_single(scope, options)
160
111
  end
161
- return timeslips.reverse
162
- end
163
-
164
- def self.find(timeslip_id)
165
- get '/timeslips'+timeslip_id
166
- timeslips = parse('timeslip', @elements)
167
- return timeslips[0]
168
- end
112
+ end
169
113
  end
170
-
171
-
114
+
172
115
  end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class FreeagentApiTest < Test::Unit::TestCase
4
+
5
+ context "Authentication details" do
6
+ should "match what was set" do
7
+ assert_equal URI.parse('https://testuser.freeagentcentral.com'), Base.site
8
+ assert_equal 'testuser', Base.user
9
+ assert_equal 'testpass', Base.password
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ class CompanyTest < Test::Unit::TestCase
4
+
5
+ fake_it_all
6
+
7
+ context "Invoice timeline" do
8
+ setup do
9
+ @inv_timeline = Company.invoice_timeline
10
+ end
11
+ should "return an array" do
12
+ assert @inv_timeline.is_a? Array
13
+ end
14
+ should "return invoice timeline items" do
15
+ assert_equal 12, @inv_timeline.size
16
+ assert @inv_timeline.first.is_a? InvoiceTimeline
17
+ end
18
+ end
19
+
20
+ context "Tax timeline" do
21
+ setup do
22
+ @tax_timeline = Company.tax_timeline
23
+ end
24
+ should "return an array" do
25
+ assert @tax_timeline.is_a? Array
26
+ end
27
+ should "return projects" do
28
+ assert_equal 3, @tax_timeline.size
29
+ assert @tax_timeline.first.is_a? TaxTimeline
30
+ end
31
+ end
32
+
33
+ end