You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

729 lines
23 KiB

  1. # Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
  2. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  3. from datetime import datetime
  4. from dateutil.relativedelta import relativedelta
  5. from decimal import Decimal
  6. import json
  7. from unittest import mock
  8. from urllib.error import HTTPError
  9. from odoo import fields
  10. from odoo.exceptions import UserError
  11. from odoo.tests import common
  12. _module_ns = 'odoo.addons.account_bank_statement_import_online_paypal'
  13. _provider_class = (
  14. _module_ns
  15. + '.models.online_bank_statement_provider_paypal'
  16. + '.OnlineBankStatementProviderPayPal'
  17. )
  18. class FakeHTTPError(HTTPError):
  19. def __init__(self, content):
  20. self.content = content
  21. def read(self):
  22. return self.content.encode('utf-8')
  23. class UrlopenRetValMock:
  24. def __init__(self, content, throw=False):
  25. self.content = content
  26. self.throw = throw
  27. def __enter__(self):
  28. return self
  29. def __exit__(self, type, value, tb):
  30. pass
  31. def read(self):
  32. if self.throw:
  33. raise FakeHTTPError(self.content)
  34. return self.content.encode('utf-8')
  35. class TestAccountBankAccountStatementImportOnlinePayPal(
  36. common.TransactionCase
  37. ):
  38. def setUp(self):
  39. super().setUp()
  40. self.now = fields.Datetime.now()
  41. self.currency_eur = self.env.ref('base.EUR')
  42. self.currency_usd = self.env.ref('base.USD')
  43. self.AccountJournal = self.env['account.journal']
  44. self.OnlineBankStatementProvider = self.env[
  45. 'online.bank.statement.provider'
  46. ]
  47. self.AccountBankStatement = self.env['account.bank.statement']
  48. self.AccountBankStatementLine = self.env['account.bank.statement.line']
  49. Provider = self.OnlineBankStatementProvider
  50. self.paypal_parse_transaction = lambda payload: (
  51. Provider._paypal_transaction_to_lines(
  52. Provider._paypal_preparse_transaction(
  53. json.loads(
  54. payload,
  55. parse_float=Decimal,
  56. )
  57. )
  58. )
  59. )
  60. self.mock_token = lambda: mock.patch(
  61. _provider_class + '._paypal_get_token',
  62. return_value='--TOKEN--',
  63. )
  64. def test_good_token(self):
  65. journal = self.AccountJournal.create({
  66. 'name': 'Bank',
  67. 'type': 'bank',
  68. 'code': 'BANK',
  69. 'currency_id': self.currency_eur.id,
  70. 'bank_statements_source': 'online',
  71. 'online_bank_statement_provider': 'paypal',
  72. })
  73. provider = journal.online_bank_statement_provider_id
  74. mocked_response = json.loads("""{
  75. "scope": "https://uri.paypal.com/services/reporting/search/read",
  76. "access_token": "---TOKEN---",
  77. "token_type": "Bearer",
  78. "app_id": "APP-1234567890",
  79. "expires_in": 32400,
  80. "nonce": "---NONCE---"
  81. }""", parse_float=Decimal)
  82. token = None
  83. with mock.patch(
  84. _provider_class + '._paypal_retrieve',
  85. return_value=mocked_response,
  86. ):
  87. token = provider._paypal_get_token()
  88. self.assertEqual(token, '---TOKEN---')
  89. def test_bad_token_scope(self):
  90. journal = self.AccountJournal.create({
  91. 'name': 'Bank',
  92. 'type': 'bank',
  93. 'code': 'BANK',
  94. 'currency_id': self.currency_eur.id,
  95. 'bank_statements_source': 'online',
  96. 'online_bank_statement_provider': 'paypal',
  97. })
  98. provider = journal.online_bank_statement_provider_id
  99. mocked_response = json.loads("""{
  100. "scope": "openid https://uri.paypal.com/services/applications/webhooks",
  101. "access_token": "---TOKEN---",
  102. "token_type": "Bearer",
  103. "app_id": "APP-1234567890",
  104. "expires_in": 32400,
  105. "nonce": "---NONCE---"
  106. }""", parse_float=Decimal)
  107. with mock.patch(
  108. _provider_class + '._paypal_retrieve',
  109. return_value=mocked_response,
  110. ):
  111. with self.assertRaises(Exception):
  112. provider._paypal_get_token()
  113. def test_bad_token_type(self):
  114. journal = self.AccountJournal.create({
  115. 'name': 'Bank',
  116. 'type': 'bank',
  117. 'code': 'BANK',
  118. 'currency_id': self.currency_eur.id,
  119. 'bank_statements_source': 'online',
  120. 'online_bank_statement_provider': 'paypal',
  121. })
  122. provider = journal.online_bank_statement_provider_id
  123. mocked_response = json.loads("""{
  124. "scope": "https://uri.paypal.com/services/reporting/search/read",
  125. "access_token": "---TOKEN---",
  126. "token_type": "NotBearer",
  127. "app_id": "APP-1234567890",
  128. "expires_in": 32400,
  129. "nonce": "---NONCE---"
  130. }""", parse_float=Decimal)
  131. with mock.patch(
  132. _provider_class + '._paypal_retrieve',
  133. return_value=mocked_response,
  134. ):
  135. with self.assertRaises(Exception):
  136. provider._paypal_get_token()
  137. def test_no_token(self):
  138. journal = self.AccountJournal.create({
  139. 'name': 'Bank',
  140. 'type': 'bank',
  141. 'code': 'BANK',
  142. 'currency_id': self.currency_eur.id,
  143. 'bank_statements_source': 'online',
  144. 'online_bank_statement_provider': 'paypal',
  145. })
  146. provider = journal.online_bank_statement_provider_id
  147. mocked_response = json.loads("""{
  148. "scope": "https://uri.paypal.com/services/reporting/search/read",
  149. "token_type": "Bearer",
  150. "app_id": "APP-1234567890",
  151. "expires_in": 32400,
  152. "nonce": "---NONCE---"
  153. }""", parse_float=Decimal)
  154. with mock.patch(
  155. _provider_class + '._paypal_retrieve',
  156. return_value=mocked_response,
  157. ):
  158. with self.assertRaises(Exception):
  159. provider._paypal_get_token()
  160. def test_no_data_on_monday(self):
  161. journal = self.AccountJournal.create({
  162. 'name': 'Bank',
  163. 'type': 'bank',
  164. 'code': 'BANK',
  165. 'currency_id': self.currency_eur.id,
  166. 'bank_statements_source': 'online',
  167. 'online_bank_statement_provider': 'paypal',
  168. })
  169. provider = journal.online_bank_statement_provider_id
  170. mocked_response_1 = UrlopenRetValMock("""{
  171. "debug_id": "eec890ebd5798",
  172. "details": "xxxxxx",
  173. "links": "xxxxxx",
  174. "message": "Data for the given start date is not available.",
  175. "name": "INVALID_REQUEST"
  176. }""", throw=True)
  177. mocked_response_2 = UrlopenRetValMock("""{
  178. "balances": [
  179. {
  180. "currency": "EUR",
  181. "primary": true,
  182. "total_balance": {
  183. "currency_code": "EUR",
  184. "value": "0.75"
  185. },
  186. "available_balance": {
  187. "currency_code": "EUR",
  188. "value": "0.75"
  189. },
  190. "withheld_balance": {
  191. "currency_code": "EUR",
  192. "value": "0.00"
  193. }
  194. }
  195. ],
  196. "account_id": "1234567890",
  197. "as_of_time": "2019-08-01T00:00:00+0000",
  198. "last_refresh_time": "2019-08-01T00:00:00+0000"
  199. }""")
  200. with mock.patch(
  201. _provider_class + '._paypal_urlopen',
  202. side_effect=[mocked_response_1, mocked_response_2],
  203. ), self.mock_token():
  204. data = provider.with_context(
  205. test_account_bank_statement_import_online_paypal_monday=True,
  206. )._obtain_statement_data(
  207. self.now - relativedelta(hours=1),
  208. self.now,
  209. )
  210. self.assertEqual(data, ([], {
  211. 'balance_start': 0.75,
  212. 'balance_end_real': 0.75,
  213. }))
  214. def test_error_handling_1(self):
  215. journal = self.AccountJournal.create({
  216. 'name': 'Bank',
  217. 'type': 'bank',
  218. 'code': 'BANK',
  219. 'currency_id': self.currency_eur.id,
  220. 'bank_statements_source': 'online',
  221. 'online_bank_statement_provider': 'paypal',
  222. })
  223. provider = journal.online_bank_statement_provider_id
  224. mocked_response = UrlopenRetValMock("""{
  225. "message": "MESSAGE",
  226. "name": "ERROR"
  227. }""", throw=True)
  228. with mock.patch(
  229. _provider_class + '._paypal_urlopen',
  230. return_value=mocked_response,
  231. ):
  232. with self.assertRaises(UserError):
  233. provider._paypal_retrieve('https://url', '')
  234. def test_error_handling_2(self):
  235. journal = self.AccountJournal.create({
  236. 'name': 'Bank',
  237. 'type': 'bank',
  238. 'code': 'BANK',
  239. 'currency_id': self.currency_eur.id,
  240. 'bank_statements_source': 'online',
  241. 'online_bank_statement_provider': 'paypal',
  242. })
  243. provider = journal.online_bank_statement_provider_id
  244. mocked_response = UrlopenRetValMock("""{
  245. "error_description": "ERROR DESCRIPTION",
  246. "error": "ERROR"
  247. }""", throw=True)
  248. with mock.patch(
  249. _provider_class + '._paypal_urlopen',
  250. return_value=mocked_response,
  251. ):
  252. with self.assertRaises(UserError):
  253. provider._paypal_retrieve('https://url', '')
  254. def test_empty_pull(self):
  255. journal = self.AccountJournal.create({
  256. 'name': 'Bank',
  257. 'type': 'bank',
  258. 'code': 'BANK',
  259. 'currency_id': self.currency_eur.id,
  260. 'bank_statements_source': 'online',
  261. 'online_bank_statement_provider': 'paypal',
  262. })
  263. provider = journal.online_bank_statement_provider_id
  264. mocked_response_1 = json.loads("""{
  265. "transaction_details": [],
  266. "account_number": "1234567890",
  267. "start_date": "2019-08-01T00:00:00+0000",
  268. "end_date": "2019-08-01T00:00:00+0000",
  269. "last_refreshed_datetime": "2019-09-01T00:00:00+0000",
  270. "page": 1,
  271. "total_items": 0,
  272. "total_pages": 0
  273. }""", parse_float=Decimal)
  274. mocked_response_2 = json.loads("""{
  275. "balances": [
  276. {
  277. "currency": "EUR",
  278. "primary": true,
  279. "total_balance": {
  280. "currency_code": "EUR",
  281. "value": "0.75"
  282. },
  283. "available_balance": {
  284. "currency_code": "EUR",
  285. "value": "0.75"
  286. },
  287. "withheld_balance": {
  288. "currency_code": "EUR",
  289. "value": "0.00"
  290. }
  291. }
  292. ],
  293. "account_id": "1234567890",
  294. "as_of_time": "2019-08-01T00:00:00+0000",
  295. "last_refresh_time": "2019-08-01T00:00:00+0000"
  296. }""", parse_float=Decimal)
  297. with mock.patch(
  298. _provider_class + '._paypal_retrieve',
  299. side_effect=[mocked_response_1, mocked_response_2],
  300. ), self.mock_token():
  301. data = provider._obtain_statement_data(
  302. self.now - relativedelta(hours=1),
  303. self.now,
  304. )
  305. self.assertEqual(data, ([], {
  306. 'balance_start': 0.75,
  307. 'balance_end_real': 0.75,
  308. }))
  309. def test_ancient_pull(self):
  310. journal = self.AccountJournal.create({
  311. 'name': 'Bank',
  312. 'type': 'bank',
  313. 'code': 'BANK',
  314. 'currency_id': self.currency_eur.id,
  315. 'bank_statements_source': 'online',
  316. 'online_bank_statement_provider': 'paypal',
  317. })
  318. provider = journal.online_bank_statement_provider_id
  319. mocked_response = json.loads("""{
  320. "transaction_details": [],
  321. "account_number": "1234567890",
  322. "start_date": "2019-08-01T00:00:00+0000",
  323. "end_date": "2019-08-01T00:00:00+0000",
  324. "last_refreshed_datetime": "2019-09-01T00:00:00+0000",
  325. "page": 1,
  326. "total_items": 0,
  327. "total_pages": 0
  328. }""", parse_float=Decimal)
  329. with mock.patch(
  330. _provider_class + '._paypal_retrieve',
  331. return_value=mocked_response,
  332. ), self.mock_token():
  333. with self.assertRaises(Exception):
  334. provider._obtain_statement_data(
  335. self.now - relativedelta(years=5),
  336. self.now,
  337. )
  338. def test_pull(self):
  339. journal = self.AccountJournal.create({
  340. 'name': 'Bank',
  341. 'type': 'bank',
  342. 'code': 'BANK',
  343. 'currency_id': self.currency_eur.id,
  344. 'bank_statements_source': 'online',
  345. 'online_bank_statement_provider': 'paypal',
  346. })
  347. provider = journal.online_bank_statement_provider_id
  348. mocked_response = json.loads("""{
  349. "transaction_details": [{
  350. "transaction_info": {
  351. "paypal_account_id": "1234567890",
  352. "transaction_id": "1234567890",
  353. "transaction_event_code": "T1234",
  354. "transaction_initiation_date": "2019-08-01T00:00:00+0000",
  355. "transaction_updated_date": "2019-08-01T00:00:00+0000",
  356. "transaction_amount": {
  357. "currency_code": "USD",
  358. "value": "1000.00"
  359. },
  360. "fee_amount": {
  361. "currency_code": "USD",
  362. "value": "-100.00"
  363. },
  364. "transaction_status": "S",
  365. "transaction_subject": "Payment for Invoice(s) 1",
  366. "ending_balance": {
  367. "currency_code": "USD",
  368. "value": "900.00"
  369. },
  370. "available_balance": {
  371. "currency_code": "USD",
  372. "value": "900.00"
  373. },
  374. "invoice_id": "1"
  375. },
  376. "payer_info": {
  377. "account_id": "1234567890",
  378. "email_address": "partner@example.com",
  379. "address_status": "Y",
  380. "payer_status": "N",
  381. "payer_name": {
  382. "alternate_full_name": "Acme, Inc."
  383. },
  384. "country_code": "US"
  385. },
  386. "shipping_info": {},
  387. "cart_info": {},
  388. "store_info": {},
  389. "auction_info": {},
  390. "incentive_info": {}
  391. }, {
  392. "transaction_info": {
  393. "paypal_account_id": "1234567890",
  394. "transaction_id": "1234567891",
  395. "transaction_event_code": "T1234",
  396. "transaction_initiation_date": "2019-08-02T00:00:00+0000",
  397. "transaction_updated_date": "2019-08-02T00:00:00+0000",
  398. "transaction_amount": {
  399. "currency_code": "USD",
  400. "value": "1000.00"
  401. },
  402. "fee_amount": {
  403. "currency_code": "USD",
  404. "value": "-100.00"
  405. },
  406. "transaction_status": "S",
  407. "transaction_subject": "Payment for Invoice(s) 1",
  408. "ending_balance": {
  409. "currency_code": "USD",
  410. "value": "900.00"
  411. },
  412. "available_balance": {
  413. "currency_code": "USD",
  414. "value": "900.00"
  415. },
  416. "invoice_id": "1"
  417. },
  418. "payer_info": {
  419. "account_id": "1234567890",
  420. "email_address": "partner@example.com",
  421. "address_status": "Y",
  422. "payer_status": "N",
  423. "payer_name": {
  424. "alternate_full_name": "Acme, Inc."
  425. },
  426. "country_code": "US"
  427. },
  428. "shipping_info": {},
  429. "cart_info": {},
  430. "store_info": {},
  431. "auction_info": {},
  432. "incentive_info": {}
  433. }],
  434. "account_number": "1234567890",
  435. "start_date": "2019-08-01T00:00:00+0000",
  436. "end_date": "2019-08-02T00:00:00+0000",
  437. "last_refreshed_datetime": "2019-09-01T00:00:00+0000",
  438. "page": 1,
  439. "total_items": 1,
  440. "total_pages": 1
  441. }""", parse_float=Decimal)
  442. with mock.patch(
  443. _provider_class + '._paypal_retrieve',
  444. return_value=mocked_response,
  445. ), self.mock_token():
  446. data = provider._obtain_statement_data(
  447. datetime(2019, 8, 1),
  448. datetime(2019, 8, 2),
  449. )
  450. self.assertEqual(len(data[0]), 2)
  451. self.assertEqual(data[0][0], {
  452. 'date': datetime(2019, 8, 1),
  453. 'amount': '1000.00',
  454. 'name': 'Invoice 1',
  455. 'note': '1234567890: Payment for Invoice(s) 1',
  456. 'partner_name': 'Acme, Inc.',
  457. 'unique_import_id': '1234567890-1564617600',
  458. })
  459. self.assertEqual(data[0][1], {
  460. 'date': datetime(2019, 8, 1),
  461. 'amount': '-100.00',
  462. 'name': 'Fee for Invoice 1',
  463. 'note': 'Transaction fee for 1234567890: Payment for Invoice(s) 1',
  464. 'partner_name': 'PayPal',
  465. 'unique_import_id': '1234567890-1564617600-FEE',
  466. })
  467. self.assertEqual(data[1], {
  468. 'balance_start': 0.0,
  469. 'balance_end_real': 900.0,
  470. })
  471. def test_transaction_parse_1(self):
  472. lines = self.paypal_parse_transaction("""{
  473. "transaction_info": {
  474. "paypal_account_id": "1234567890",
  475. "transaction_id": "1234567890",
  476. "transaction_event_code": "T1234",
  477. "transaction_initiation_date": "2019-08-01T00:00:00+0000",
  478. "transaction_updated_date": "2019-08-01T00:00:00+0000",
  479. "transaction_amount": {
  480. "currency_code": "USD",
  481. "value": "1000.00"
  482. },
  483. "fee_amount": {
  484. "currency_code": "USD",
  485. "value": "0.00"
  486. },
  487. "transaction_status": "S",
  488. "transaction_subject": "Payment for Invoice(s) 1",
  489. "ending_balance": {
  490. "currency_code": "USD",
  491. "value": "1000.00"
  492. },
  493. "available_balance": {
  494. "currency_code": "USD",
  495. "value": "1000.00"
  496. },
  497. "invoice_id": "1"
  498. },
  499. "payer_info": {
  500. "account_id": "1234567890",
  501. "email_address": "partner@example.com",
  502. "address_status": "Y",
  503. "payer_status": "N",
  504. "payer_name": {
  505. "alternate_full_name": "Acme, Inc."
  506. },
  507. "country_code": "US"
  508. },
  509. "shipping_info": {},
  510. "cart_info": {},
  511. "store_info": {},
  512. "auction_info": {},
  513. "incentive_info": {}
  514. }""")
  515. self.assertEqual(len(lines), 1)
  516. self.assertEqual(lines[0], {
  517. 'date': datetime(2019, 8, 1),
  518. 'amount': '1000.00',
  519. 'name': 'Invoice 1',
  520. 'note': '1234567890: Payment for Invoice(s) 1',
  521. 'partner_name': 'Acme, Inc.',
  522. 'unique_import_id': '1234567890-1564617600',
  523. })
  524. def test_transaction_parse_2(self):
  525. lines = self.paypal_parse_transaction("""{
  526. "transaction_info": {
  527. "paypal_account_id": "1234567890",
  528. "transaction_id": "1234567890",
  529. "transaction_event_code": "T1234",
  530. "transaction_initiation_date": "2019-08-01T00:00:00+0000",
  531. "transaction_updated_date": "2019-08-01T00:00:00+0000",
  532. "transaction_amount": {
  533. "currency_code": "USD",
  534. "value": "1000.00"
  535. },
  536. "fee_amount": {
  537. "currency_code": "USD",
  538. "value": "0.00"
  539. },
  540. "transaction_status": "S",
  541. "transaction_subject": "Payment for Invoice(s) 1",
  542. "ending_balance": {
  543. "currency_code": "USD",
  544. "value": "1000.00"
  545. },
  546. "available_balance": {
  547. "currency_code": "USD",
  548. "value": "1000.00"
  549. },
  550. "invoice_id": "1"
  551. },
  552. "payer_info": {
  553. "account_id": "1234567890",
  554. "email_address": "partner@example.com",
  555. "address_status": "Y",
  556. "payer_status": "N",
  557. "payer_name": {
  558. "alternate_full_name": "Acme, Inc."
  559. },
  560. "country_code": "US"
  561. },
  562. "shipping_info": {},
  563. "cart_info": {},
  564. "store_info": {},
  565. "auction_info": {},
  566. "incentive_info": {}
  567. }""")
  568. self.assertEqual(len(lines), 1)
  569. self.assertEqual(lines[0], {
  570. 'date': datetime(2019, 8, 1),
  571. 'amount': '1000.00',
  572. 'name': 'Invoice 1',
  573. 'note': '1234567890: Payment for Invoice(s) 1',
  574. 'partner_name': 'Acme, Inc.',
  575. 'unique_import_id': '1234567890-1564617600',
  576. })
  577. def test_transaction_parse_3(self):
  578. lines = self.paypal_parse_transaction("""{
  579. "transaction_info": {
  580. "paypal_account_id": "1234567890",
  581. "transaction_id": "1234567890",
  582. "transaction_event_code": "T1234",
  583. "transaction_initiation_date": "2019-08-01T00:00:00+0000",
  584. "transaction_updated_date": "2019-08-01T00:00:00+0000",
  585. "transaction_amount": {
  586. "currency_code": "USD",
  587. "value": "1000.00"
  588. },
  589. "fee_amount": {
  590. "currency_code": "USD",
  591. "value": "-100.00"
  592. },
  593. "transaction_status": "S",
  594. "transaction_subject": "Payment for Invoice(s) 1",
  595. "ending_balance": {
  596. "currency_code": "USD",
  597. "value": "900.00"
  598. },
  599. "available_balance": {
  600. "currency_code": "USD",
  601. "value": "900.00"
  602. },
  603. "invoice_id": "1"
  604. },
  605. "payer_info": {
  606. "account_id": "1234567890",
  607. "email_address": "partner@example.com",
  608. "address_status": "Y",
  609. "payer_status": "N",
  610. "payer_name": {
  611. "alternate_full_name": "Acme, Inc."
  612. },
  613. "country_code": "US"
  614. },
  615. "shipping_info": {},
  616. "cart_info": {},
  617. "store_info": {},
  618. "auction_info": {},
  619. "incentive_info": {}
  620. }""")
  621. self.assertEqual(len(lines), 2)
  622. self.assertEqual(lines[0], {
  623. 'date': datetime(2019, 8, 1),
  624. 'amount': '1000.00',
  625. 'name': 'Invoice 1',
  626. 'note': '1234567890: Payment for Invoice(s) 1',
  627. 'partner_name': 'Acme, Inc.',
  628. 'unique_import_id': '1234567890-1564617600',
  629. })
  630. self.assertEqual(lines[1], {
  631. 'date': datetime(2019, 8, 1),
  632. 'amount': '-100.00',
  633. 'name': 'Fee for Invoice 1',
  634. 'note': 'Transaction fee for 1234567890: Payment for Invoice(s) 1',
  635. 'partner_name': 'PayPal',
  636. 'unique_import_id': '1234567890-1564617600-FEE',
  637. })
  638. def test_transaction_parse_4(self):
  639. lines = self.paypal_parse_transaction("""{
  640. "transaction_info": {
  641. "paypal_account_id": "1234567890",
  642. "transaction_id": "1234567890",
  643. "transaction_event_code": "T1234",
  644. "transaction_initiation_date": "2019-08-01T00:00:00+0000",
  645. "transaction_updated_date": "2019-08-01T00:00:00+0000",
  646. "transaction_amount": {
  647. "currency_code": "USD",
  648. "value": "1000.00"
  649. },
  650. "transaction_status": "S",
  651. "transaction_subject": "Payment for Invoice(s) 1",
  652. "ending_balance": {
  653. "currency_code": "USD",
  654. "value": "1000.00"
  655. },
  656. "available_balance": {
  657. "currency_code": "USD",
  658. "value": "1000.00"
  659. },
  660. "invoice_id": "1"
  661. },
  662. "payer_info": {
  663. "account_id": "1234567890",
  664. "email_address": "partner@example.com",
  665. "address_status": "Y",
  666. "payer_status": "N",
  667. "payer_name": {
  668. "alternate_full_name": "Acme, Inc."
  669. },
  670. "country_code": "US"
  671. },
  672. "shipping_info": {},
  673. "cart_info": {},
  674. "store_info": {},
  675. "auction_info": {},
  676. "incentive_info": {}
  677. }""")
  678. self.assertEqual(len(lines), 1)
  679. self.assertEqual(lines[0], {
  680. 'date': datetime(2019, 8, 1),
  681. 'amount': '1000.00',
  682. 'name': 'Invoice 1',
  683. 'note': '1234567890: Payment for Invoice(s) 1',
  684. 'partner_name': 'Acme, Inc.',
  685. 'unique_import_id': '1234567890-1564617600',
  686. })