PageRenderTime 27ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/invoice_test.rb

http://github.com/tlconnor/xero_gateway
Ruby | 435 lines | 289 code | 81 blank | 65 comment | 7 complexity | 02a7428354bd7cc49e74b9c6fe86cc4e MD5 | raw file
Possible License(s): 0BSD
  1. require File.join(File.dirname(__FILE__), '../test_helper.rb')
  2. class InvoiceTest < Test::Unit::TestCase
  3. context "with line item totals" do
  4. setup do
  5. # make sure the invoices think they have a gateway
  6. @invoice = create_test_invoice(
  7. invoice_id: "a99a9aaa-9999-99a9-9aa9-aaaaaa9a9999",
  8. line_items_downloaded: false,
  9. total: 6_969_00,
  10. total_tax: 1_045.35,
  11. sub_total: 5_923.65
  12. )
  13. @invoice.gateway = stub
  14. end
  15. should "allow setting and reading these as instance variables without downloading line items" do
  16. assert !@invoice.line_items_downloaded?
  17. XeroGateway::Invoice.any_instance.expects(:download_line_items).never
  18. assert_equal 6969_00, @invoice.total
  19. assert_equal 1_045.35, @invoice.total_tax
  20. assert_equal 5_923.65, @invoice.sub_total
  21. end
  22. should "download line items if we call #line_items" do
  23. XeroGateway::Invoice.any_instance.expects(:download_line_items).once.returns([])
  24. assert_equal [], @invoice.line_items
  25. end
  26. should "also work when creating an invoice from XML" do
  27. invoices_response_xml = File.open(File.join(File.dirname(__FILE__), "..", "stub_responses", "invoices.xml")).read
  28. invoice_element = REXML::XPath.first(REXML::Document.new(invoices_response_xml), "/Response/Invoices/Invoice")
  29. from_xml = XeroGateway::Invoice.from_xml(invoice_element)
  30. from_xml.gateway = stub()
  31. assert !from_xml.line_items_downloaded?
  32. XeroGateway::Invoice.any_instance.expects(:download_line_items).never
  33. assert_equal 1125.0, from_xml.total
  34. assert_equal 125.0, from_xml.total_tax
  35. assert_equal 1000.0, from_xml.sub_total
  36. end
  37. end
  38. context "building and parsing XML" do
  39. should "work vice versa" do
  40. invoice = create_test_invoice
  41. # Generate the XML message
  42. invoice_as_xml = invoice.to_xml
  43. # Parse the XML message and retrieve the invoice element
  44. invoice_element = REXML::XPath.first(REXML::Document.new(invoice_as_xml), "/Invoice")
  45. # Build a new invoice from the XML
  46. result_invoice = XeroGateway::Invoice.from_xml(invoice_element)
  47. assert_equal(invoice, result_invoice)
  48. end
  49. should "work for optional params" do
  50. invoice = create_test_invoice(:url => 'http://example.com?with=params&and=more')
  51. invoice_element = REXML::XPath.first(REXML::Document.new(invoice.to_xml), "/Invoice")
  52. assert_match(/<Url>http:\/\/example.com\?with=params&amp;and=more<\/Url>/, invoice_element.to_s)
  53. parsed_invoice = XeroGateway::Invoice.from_xml(invoice_element)
  54. assert_equal 'http://example.com?with=params&and=more', parsed_invoice.url
  55. end
  56. should "work with line_item discount rates" do
  57. invoice = create_test_invoice
  58. invoice.line_items.first.discount_rate = 27
  59. invoice_as_xml = invoice.to_xml
  60. invoice_element = REXML::XPath.first(REXML::Document.new(invoice_as_xml), "/Invoice")
  61. result_invoice = XeroGateway::Invoice.from_xml(invoice_element)
  62. assert_equal(invoice, result_invoice)
  63. assert_equal 27, result_invoice.line_items.first.discount_rate
  64. end
  65. should "handle paid-on date" do
  66. invoice = create_test_invoice(:fully_paid_on => Date.yesterday)
  67. invoice_element = REXML::XPath.first(REXML::Document.new(invoice.to_xml), "/Invoice")
  68. result_invoice = XeroGateway::Invoice.from_xml(invoice_element)
  69. assert_equal(invoice, result_invoice)
  70. assert_equal Date.yesterday, result_invoice.fully_paid_on
  71. end
  72. end
  73. # Tests the sub_total calculation and that setting it manually doesn't modify the data.
  74. def test_invoice_sub_total_calculation
  75. invoice = create_test_invoice(:line_items_downloaded => true)
  76. line_item = invoice.line_items.first
  77. # Make sure that everything adds up to begin with.
  78. expected_sub_total = invoice.line_items.inject(BigDecimal('0')) { | sum, l | l.line_amount }
  79. assert_equal(expected_sub_total, invoice.sub_total)
  80. # Change the sub_total and check that it doesn't modify anything.
  81. invoice.sub_total = expected_sub_total * 10
  82. assert_equal(expected_sub_total, invoice.sub_total)
  83. # Change the amount of the first line item and make sure that
  84. # everything still continues to add up.
  85. line_item.unit_amount = line_item.unit_amount + 10
  86. assert_not_equal(expected_sub_total, invoice.sub_total)
  87. expected_sub_total = invoice.line_items.inject(BigDecimal('0')) { | sum, l | l.line_amount }
  88. assert_equal(expected_sub_total, invoice.sub_total)
  89. end
  90. # Tests the total_tax calculation and that setting it manually doesn't modify the data.
  91. def test_invoice_sub_total_calculation2
  92. invoice = create_test_invoice(:line_items_downloaded => true)
  93. line_item = invoice.line_items.first
  94. # Make sure that everything adds up to begin with.
  95. expected_total_tax = invoice.line_items.inject(BigDecimal('0')) { | sum, l | l.tax_amount }
  96. assert_equal(expected_total_tax, invoice.total_tax)
  97. # Change the total_tax and check that it doesn't modify anything.
  98. invoice.total_tax = expected_total_tax * 10
  99. assert_equal(expected_total_tax, invoice.total_tax)
  100. # Change the tax_amount of the first line item and make sure that
  101. # everything still continues to add up.
  102. line_item.tax_amount = line_item.tax_amount + 10
  103. assert_not_equal(expected_total_tax, invoice.total_tax)
  104. expected_total_tax = invoice.line_items.inject(BigDecimal('0')) { | sum, l | l.tax_amount }
  105. assert_equal(expected_total_tax, invoice.total_tax)
  106. end
  107. # Tests the total calculation and that setting it manually doesn't modify the data.
  108. def test_invoice_sub_total_calculation3
  109. invoice = create_test_invoice(:line_items_downloaded => true)
  110. assert invoice.line_items_downloaded?
  111. line_item = invoice.line_items.first
  112. # Make sure that everything adds up to begin with.
  113. expected_total = invoice.sub_total + invoice.total_tax
  114. assert_equal(expected_total, invoice.total)
  115. # Change the total and check that it doesn't modify anything.
  116. invoice.total = expected_total * 10
  117. assert_equal(expected_total.to_f, invoice.total.to_f)
  118. # Change the quantity of the first line item and make sure that
  119. # everything still continues to add up.
  120. line_item.quantity = line_item.quantity + 5
  121. assert_not_equal(expected_total, invoice.total)
  122. expected_total = invoice.sub_total + invoice.total_tax
  123. assert_equal(expected_total, invoice.total)
  124. end
  125. # Tests that the LineItem#line_amount calculation is working correctly.
  126. def test_line_amount_calculation
  127. invoice = create_test_invoice
  128. line_item = invoice.line_items.first
  129. # Make sure that everything adds up to begin with.
  130. expected_amount = line_item.quantity * line_item.unit_amount
  131. assert_equal(expected_amount, line_item.line_amount)
  132. # Change the line_amount and check that it doesn't modify anything.
  133. line_item.line_amount = expected_amount * 10
  134. assert_equal(expected_amount, line_item.line_amount)
  135. # Change the quantity and check that the line_amount has been updated.
  136. quantity = line_item.quantity + 2
  137. line_item.quantity = quantity
  138. assert_not_equal(expected_amount, line_item.line_amount)
  139. assert_equal(quantity * line_item.unit_amount, line_item.line_amount)
  140. end
  141. def test_line_amount_discount_calculation
  142. invoice = create_test_invoice
  143. line_item = invoice.line_items.first
  144. line_item.discount_rate = 12.5
  145. # Make sure that everything adds up to begin with.
  146. expected_amount = line_item.quantity * line_item.unit_amount * 0.875
  147. assert_equal(expected_amount, line_item.line_amount)
  148. # Change the line_amount and check that it doesn't modify anything.
  149. line_item.line_amount = expected_amount * 10
  150. assert_equal(expected_amount, line_item.line_amount)
  151. # Change the quantity and check that the line_amount has been updated.
  152. quantity = line_item.quantity + 2
  153. line_item.quantity = quantity
  154. assert_not_equal(expected_amount, line_item.line_amount)
  155. assert_equal(quantity * line_item.unit_amount * 0.875, line_item.line_amount)
  156. end
  157. # Ensure that the totalling methods don't raise exceptions, even when
  158. # invoice.line_items is empty.
  159. def test_totalling_methods_when_line_items_empty
  160. invoice = create_test_invoice
  161. invoice.line_items = []
  162. assert_nothing_raised(Exception) {
  163. assert_equal(BigDecimal('0'), invoice.sub_total)
  164. assert_equal(BigDecimal('0'), invoice.total_tax)
  165. assert_equal(BigDecimal('0'), invoice.total)
  166. }
  167. end
  168. def test_invoice_type_helper_methods
  169. # Test accounts receivable invoices.
  170. invoice = create_test_invoice({:invoice_type => 'ACCREC'})
  171. assert_equal(true, invoice.accounts_receivable?, "Accounts RECEIVABLE invoice doesn't think it is.")
  172. assert_equal(false, invoice.accounts_payable?, "Accounts RECEIVABLE invoice thinks it's payable.")
  173. # Test accounts payable invoices.
  174. invoice = create_test_invoice({:invoice_type => 'ACCPAY'})
  175. assert_equal(false, invoice.accounts_receivable?, "Accounts PAYABLE invoice doesn't think it is.")
  176. assert_equal(true, invoice.accounts_payable?, "Accounts PAYABLE invoice thinks it's receivable.")
  177. end
  178. # Make sure that the create_test_invoice method is working correctly
  179. # with all the defaults and overrides.
  180. def test_create_test_invoice_defaults_working
  181. invoice = create_test_invoice
  182. # Test invoice defaults.
  183. assert_equal('ACCREC', invoice.invoice_type)
  184. assert_kind_of(Date, invoice.date)
  185. assert_kind_of(Date, invoice.due_date)
  186. assert_kind_of(Time, invoice.updated_date_utc)
  187. assert_equal('12345', invoice.invoice_number)
  188. assert_equal('MY REFERENCE FOR THIS INVOICE', invoice.reference)
  189. assert_equal("Exclusive", invoice.line_amount_types)
  190. # Test the contact defaults.
  191. assert_equal('00000000-0000-0000-0000-000000000000', invoice.contact.contact_id)
  192. assert_equal('CONTACT NAME', invoice.contact.name)
  193. # Test address defaults.
  194. assert_equal('DEFAULT', invoice.contact.address.address_type)
  195. assert_equal('LINE 1 OF THE ADDRESS', invoice.contact.address.line_1)
  196. # Test phone defaults.
  197. assert_equal('DEFAULT', invoice.contact.phone.phone_type)
  198. assert_equal('12345678', invoice.contact.phone.number)
  199. # Test the line_item defaults.
  200. assert_equal('A LINE ITEM', invoice.line_items.first.description)
  201. assert_equal('200', invoice.line_items.first.account_code)
  202. assert_equal(BigDecimal('100'), invoice.line_items.first.unit_amount)
  203. assert_equal(BigDecimal('12.5'), invoice.line_items.first.tax_amount)
  204. # Test optional params
  205. assert_nil invoice.url
  206. # Test overriding an invoice parameter (assume works for all).
  207. invoice = create_test_invoice({:invoice_type => 'ACCPAY'})
  208. assert_equal('ACCPAY', invoice.invoice_type)
  209. # Test overriding a contact/address/phone parameter (assume works for all).
  210. invoice = create_test_invoice({}, {:name => 'OVERRIDDEN NAME', :address => {:line_1 => 'OVERRIDDEN LINE 1'}, :phone => {:number => '999'}})
  211. assert_equal('OVERRIDDEN NAME', invoice.contact.name)
  212. assert_equal('OVERRIDDEN LINE 1', invoice.contact.address.line_1)
  213. assert_equal('999', invoice.contact.phone.number)
  214. # Test overriding line_items with hash.
  215. invoice = create_test_invoice({}, {}, {:description => 'OVERRIDDEN LINE ITEM'})
  216. assert_equal(1, invoice.line_items.size)
  217. assert_equal('OVERRIDDEN LINE ITEM', invoice.line_items.first.description)
  218. assert_equal(BigDecimal('100'), invoice.line_items.first.unit_amount)
  219. # Test overriding line_items with array of 2 line_items.
  220. invoice = create_test_invoice({}, {}, [
  221. {:description => 'OVERRIDDEN ITEM 1'},
  222. {:description => 'OVERRIDDEN ITEM 2', :account_code => '200', :unit_amount => BigDecimal('200'), :tax_amount => '25.0'}
  223. ])
  224. assert_equal(2, invoice.line_items.size)
  225. assert_equal('OVERRIDDEN ITEM 1', invoice.line_items[0].description)
  226. assert_equal(BigDecimal('100'), invoice.line_items[0].unit_amount)
  227. assert_equal('OVERRIDDEN ITEM 2', invoice.line_items[1].description)
  228. assert_equal(BigDecimal('200'), invoice.line_items[1].unit_amount)
  229. end
  230. def test_auto_creation_of_associated_contact
  231. invoice = create_test_invoice({}, nil) # no contact
  232. assert(!invoice.instance_variable_defined?("@contact"))
  233. new_contact = invoice.contact
  234. assert_kind_of(XeroGateway::Contact, new_contact)
  235. end
  236. def test_add_line_item
  237. invoice = create_test_invoice({}, {}, nil) # no line_items
  238. assert_equal(0, invoice.line_items.size)
  239. line_item_params = {:description => "Test Item 1", :unit_amount => 100}
  240. # Test adding line item by hash
  241. line_item = invoice.add_line_item(line_item_params)
  242. assert_kind_of(XeroGateway::LineItem, line_item)
  243. assert_equal(line_item_params[:description], line_item.description)
  244. assert_equal(line_item_params[:unit_amount], line_item.unit_amount)
  245. assert_equal(1, invoice.line_items.size)
  246. # Test adding line item by XeroGateway::LineItem
  247. line_item = invoice.add_line_item(line_item_params)
  248. assert_kind_of(XeroGateway::LineItem, line_item)
  249. assert_equal(line_item_params[:description], line_item.description)
  250. assert_equal(line_item_params[:unit_amount], line_item.unit_amount)
  251. assert_equal(2, invoice.line_items.size)
  252. # Test that pushing anything else into add_line_item fails.
  253. ["invalid", 100, nil, []].each do | invalid_object |
  254. assert_raise(XeroGateway::InvalidLineItemError) { invoice.add_line_item(invalid_object) }
  255. assert_equal(2, invoice.line_items.size)
  256. end
  257. end
  258. def test_instantiate_invoice_with_default_line_amount_types
  259. invoice = XeroGateway::Invoice.new
  260. assert_equal(invoice.line_amount_types, 'Exclusive')
  261. end
  262. def test_optional_params
  263. eur_code = "EUR"
  264. eur_rate = 1.80
  265. invoice = create_test_invoice(:url => 'http://example.com', :branding_theme_id => 'a94a78db-5cc6-4e26-a52b-045237e56e6e', :currency_code => eur_code, :currency_rate => eur_rate)
  266. assert_equal 'http://example.com', invoice.url
  267. assert_equal 'a94a78db-5cc6-4e26-a52b-045237e56e6e', invoice.branding_theme_id
  268. assert_equal eur_code, invoice.currency_code
  269. assert_equal eur_rate, invoice.currency_rate
  270. end
  271. def test_updated_date_utc
  272. time = Time.now.utc
  273. invoice = create_test_invoice(:updated_date_utc => time)
  274. assert_equal time, invoice.updated_date_utc
  275. end
  276. def test_description_only_lines
  277. invoice = create_test_invoice({}, {}, nil) # no line_items
  278. invoice.add_line_item(
  279. description: "Descriptoin only",
  280. unit_amount: nil,
  281. quantity: nil
  282. )
  283. assert_nil invoice.line_items[0].line_amount
  284. end
  285. private
  286. def create_test_invoice(invoice_params = {}, contact_params = {}, line_item_params = [])
  287. unless invoice_params.nil?
  288. invoice_params = {
  289. :invoice_type => 'ACCREC',
  290. :date => Date.today,
  291. :due_date => Date.today + 10, # 10 days in the future
  292. :invoice_number => '12345',
  293. :reference => "MY REFERENCE FOR THIS INVOICE",
  294. :line_amount_types => "Exclusive",
  295. :updated_date_utc => Time.now.utc
  296. }.merge(invoice_params)
  297. end
  298. invoice = XeroGateway::Invoice.new(invoice_params || {})
  299. unless contact_params.nil?
  300. # Strip out :address key from contact_params to use as the default address.
  301. stripped_address = {
  302. :address_type => 'DEFAULT',
  303. :line_1 => 'LINE 1 OF THE ADDRESS'
  304. }.merge(contact_params.delete(:address) || {})
  305. # Strip out :phone key from contact_params to use at the default phone.
  306. stripped_phone = {
  307. :phone_type => 'DEFAULT',
  308. :number => '12345678'
  309. }.merge(contact_params.delete(:phone) || {})
  310. contact_params = {
  311. :contact_id => '00000000-0000-0000-0000-000000000000', # Just any valid GUID
  312. :name => "CONTACT NAME",
  313. :first_name => "Bob",
  314. :last_name => "Builder"
  315. }.merge(contact_params)
  316. # Create invoice.contact from contact_params.
  317. invoice.contact = XeroGateway::Contact.new(contact_params)
  318. invoice.contact.address = XeroGateway::Address.new(stripped_address)
  319. invoice.contact.phone = XeroGateway::Phone.new(stripped_phone)
  320. end
  321. unless line_item_params.nil?
  322. line_item_params = [line_item_params].flatten # always use an array, even if only a single hash passed in
  323. # At least one line item, make first have some defaults.
  324. line_item_params << {} if line_item_params.size == 0
  325. line_item_params[0] = {
  326. :description => "A LINE ITEM",
  327. :account_code => "200",
  328. :unit_amount => BigDecimal("100"),
  329. :tax_amount => BigDecimal("12.5"),
  330. :tracking => XeroGateway::TrackingCategory.new(:name => "blah", :options => "hello")
  331. }.merge(line_item_params[0])
  332. # Create invoice.line_items from line_item_params
  333. line_item_params.each do | line_item |
  334. invoice.add_line_item(line_item)
  335. end
  336. end
  337. invoice
  338. end
  339. # NB: Xero no longer appears to provide XSDs for their api, check http://blog.xero.com/developer/api/invoices/
  340. #
  341. # context "validating against the Xero XSD" do
  342. # setup do
  343. # # @schema = LibXML::XML::Schema.document(LibXML::XML::Document.file(File.join(File.dirname(__FILE__), '../xsd/create_invoice.xsd')))
  344. # end
  345. #
  346. # should "succeed" do
  347. # invoice = create_test_invoice
  348. # message = invoice.to_xml
  349. #
  350. # # Check that the document matches the XSD
  351. # assert LibXML::XML::Parser.string(message).parse.validate_schema(@schema), "The XML document generated did not validate against the XSD"
  352. # end
  353. # end
  354. end