Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

test_basket.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. import datetime
  2. from decimal import Decimal as D
  3. from http import client as http_client
  4. from http.cookies import _unquote
  5. from django.test import TestCase, override_settings
  6. from django.urls import reverse
  7. from django.utils.translation import gettext
  8. from oscar.apps.basket import reports
  9. from oscar.apps.basket.models import Basket
  10. from oscar.apps.partner import strategy
  11. from oscar.core.compat import get_user_model
  12. from oscar.test import factories
  13. from oscar.test.basket import add_product
  14. from oscar.test.factories import create_product
  15. from oscar.test.testcases import WebTestCase
  16. User = get_user_model()
  17. class TestBasketMerging(TestCase):
  18. def setUp(self):
  19. self.product = create_product(num_in_stock=10)
  20. self.user_basket = Basket()
  21. self.user_basket.strategy = strategy.Default()
  22. add_product(self.user_basket, product=self.product)
  23. self.cookie_basket = Basket()
  24. self.cookie_basket.strategy = strategy.Default()
  25. add_product(self.cookie_basket, quantity=2, product=self.product)
  26. self.user_basket.merge(self.cookie_basket, add_quantities=False)
  27. def test_cookie_basket_has_status_set(self):
  28. self.assertEqual(Basket.MERGED, self.cookie_basket.status)
  29. def test_lines_are_moved_across(self):
  30. self.assertEqual(1, self.user_basket.lines.all().count())
  31. def test_merge_line_takes_max_quantity(self):
  32. line = self.user_basket.lines.get(product=self.product)
  33. self.assertEqual(2, line.quantity)
  34. class AnonAddToBasketViewTests(WebTestCase):
  35. csrf_checks = False
  36. def setUp(self):
  37. self.product = create_product(
  38. price=D('10.00'), num_in_stock=10)
  39. url = reverse('basket:add', kwargs={'pk': self.product.pk})
  40. post_params = {'product_id': self.product.id,
  41. 'action': 'add',
  42. 'quantity': 1}
  43. self.response = self.app.post(url, params=post_params)
  44. def test_cookie_is_created(self):
  45. self.assertTrue('oscar_open_basket' in self.response.test_app.cookies)
  46. def test_price_is_recorded(self):
  47. oscar_open_basket_cookie = _unquote(self.response.test_app.cookies['oscar_open_basket'])
  48. basket_id = oscar_open_basket_cookie.split(':')[0]
  49. basket = Basket.objects.get(id=basket_id)
  50. line = basket.lines.get(product=self.product)
  51. stockrecord = self.product.stockrecords.all()[0]
  52. self.assertEqual(stockrecord.price, line.price_excl_tax)
  53. class BasketSummaryViewTests(WebTestCase):
  54. def setUp(self):
  55. url = reverse('basket:summary')
  56. self.response = self.app.get(url)
  57. def test_shipping_method_in_context(self):
  58. self.assertTrue('shipping_method' in self.response.context)
  59. def test_order_total_in_context(self):
  60. self.assertTrue('order_total' in self.response.context)
  61. def test_view_does_not_error(self):
  62. self.assertEqual(http_client.OK, self.response.status_code)
  63. def test_basket_in_context(self):
  64. self.assertTrue('basket' in self.response.context)
  65. def test_basket_is_empty(self):
  66. basket = self.response.context['basket']
  67. self.assertEqual(0, basket.num_lines)
  68. class BasketThresholdTest(WebTestCase):
  69. csrf_checks = False
  70. @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=3)
  71. def test_adding_more_than_threshold_raises(self):
  72. dummy_product = create_product(price=D('10.00'), num_in_stock=10)
  73. url = reverse('basket:add', kwargs={'pk': dummy_product.pk})
  74. post_params = {'product_id': dummy_product.id,
  75. 'action': 'add',
  76. 'quantity': 2}
  77. response = self.app.post(url, params=post_params)
  78. self.assertIn('oscar_open_basket', response.test_app.cookies)
  79. post_params = {'product_id': dummy_product.id,
  80. 'action': 'add',
  81. 'quantity': 2}
  82. response = self.app.post(url, params=post_params)
  83. expected = gettext(
  84. "Due to technical limitations we are not able to ship more "
  85. "than %(threshold)d items in one order. Your basket currently "
  86. "has %(basket)d items."
  87. ) % ({'threshold': 3, 'basket': 2})
  88. self.assertIn(expected, response.test_app.cookies['messages'])
  89. class BasketReportTests(TestCase):
  90. def test_open_report_doesnt_error(self):
  91. data = {
  92. 'start_date': datetime.date(2012, 5, 1),
  93. 'end_date': datetime.date(2012, 5, 17),
  94. 'formatter': 'CSV'
  95. }
  96. generator = reports.OpenBasketReportGenerator(**data)
  97. generator.generate()
  98. def test_submitted_report_doesnt_error(self):
  99. data = {
  100. 'start_date': datetime.date(2012, 5, 1),
  101. 'end_date': datetime.date(2012, 5, 17),
  102. 'formatter': 'CSV'
  103. }
  104. generator = reports.SubmittedBasketReportGenerator(**data)
  105. generator.generate()
  106. class SavedBasketTests(WebTestCase):
  107. csrf_checks = False
  108. def test_moving_to_saved_basket_creates_new(self):
  109. self.user = factories.UserFactory()
  110. product = factories.ProductFactory()
  111. basket = factories.BasketFactory(owner=self.user)
  112. basket.add_product(product)
  113. response = self.get(reverse('basket:summary'))
  114. formset = response.context['formset']
  115. form = formset.forms[0]
  116. data = {
  117. formset.add_prefix('INITIAL_FORMS'): 1,
  118. formset.add_prefix('TOTAL_FORMS'): 1,
  119. formset.add_prefix('MIN_FORMS'): 0,
  120. formset.add_prefix('MAX_NUM_FORMS'): 1,
  121. form.add_prefix('id'): form.instance.pk,
  122. form.add_prefix('quantity'): form.initial['quantity'],
  123. form.add_prefix('save_for_later'): True,
  124. }
  125. response = self.post(reverse('basket:summary'), params=data)
  126. self.assertRedirects(response, reverse('basket:summary'))
  127. self.assertFalse(Basket.open.get(pk=basket.pk).lines.exists())
  128. self.assertEqual(Basket.saved.get(owner=self.user).lines.get(
  129. product=product).quantity, 1)
  130. def test_moving_to_saved_basket_updates_existing(self):
  131. self.user = factories.UserFactory()
  132. product = factories.ProductFactory()
  133. basket = factories.BasketFactory(owner=self.user)
  134. basket.add_product(product)
  135. saved_basket = factories.BasketFactory(owner=self.user,
  136. status=Basket.SAVED)
  137. saved_basket.add_product(product)
  138. response = self.get(reverse('basket:summary'))
  139. formset = response.context['formset']
  140. form = formset.forms[0]
  141. data = {
  142. formset.add_prefix('INITIAL_FORMS'): 1,
  143. formset.add_prefix('TOTAL_FORMS'): 1,
  144. formset.add_prefix('MIN_FORMS'): 0,
  145. formset.add_prefix('MAX_NUM_FORMS'): 1,
  146. form.add_prefix('id'): form.instance.pk,
  147. form.add_prefix('quantity'): form.initial['quantity'],
  148. form.add_prefix('save_for_later'): True,
  149. }
  150. response = self.post(reverse('basket:summary'), params=data)
  151. self.assertRedirects(response, reverse('basket:summary'))
  152. self.assertFalse(Basket.open.get(pk=basket.pk).lines.exists())
  153. self.assertEqual(Basket.saved.get(pk=saved_basket.pk).lines.get(
  154. product=product).quantity, 2)
  155. def test_moving_from_saved_basket(self):
  156. self.user = User.objects.create_user(username='test', password='pass',
  157. email='test@example.com')
  158. product = create_product(price=D('10.00'), num_in_stock=2)
  159. basket = factories.create_basket(empty=True)
  160. basket.owner = self.user
  161. basket.save()
  162. add_product(basket, product=product)
  163. saved_basket, created = Basket.saved.get_or_create(owner=self.user)
  164. saved_basket.strategy = basket.strategy
  165. add_product(saved_basket, product=product)
  166. response = self.get(reverse('basket:summary'))
  167. saved_formset = response.context['saved_formset']
  168. saved_form = saved_formset.forms[0]
  169. data = {
  170. saved_formset.add_prefix('INITIAL_FORMS'): 1,
  171. saved_formset.add_prefix('MAX_NUM_FORMS'): 1,
  172. saved_formset.add_prefix('TOTAL_FORMS'): 1,
  173. saved_form.add_prefix('id'): saved_form.initial['id'],
  174. saved_form.add_prefix('move_to_basket'): True,
  175. }
  176. response = self.post(reverse('basket:saved'), params=data)
  177. self.assertEqual(Basket.open.get(id=basket.id).lines.get(
  178. product=product).quantity, 2)
  179. self.assertRedirects(response, reverse('basket:summary'))
  180. def test_moving_from_saved_basket_more_than_stocklevel_raises(self):
  181. self.user = User.objects.create_user(username='test', password='pass',
  182. email='test@example.com')
  183. product = create_product(price=D('10.00'), num_in_stock=1)
  184. basket, created = Basket.open.get_or_create(owner=self.user)
  185. add_product(basket, product=product)
  186. saved_basket, created = Basket.saved.get_or_create(owner=self.user)
  187. add_product(saved_basket, product=product)
  188. response = self.get(reverse('basket:summary'))
  189. saved_formset = response.context['saved_formset']
  190. saved_form = saved_formset.forms[0]
  191. data = {
  192. saved_formset.add_prefix('INITIAL_FORMS'): 1,
  193. saved_formset.add_prefix('MAX_NUM_FORMS'): 1,
  194. saved_formset.add_prefix('TOTAL_FORMS'): 1,
  195. saved_form.add_prefix('id'): saved_form.initial['id'],
  196. saved_form.add_prefix('move_to_basket'): True,
  197. }
  198. response = self.post(reverse('basket:saved'), params=data)
  199. # we can't add more than stock level into basket
  200. self.assertEqual(Basket.open.get(id=basket.id).lines.get(product=product).quantity, 1)
  201. self.assertRedirects(response, reverse('basket:summary'))
  202. class BasketFormSetTests(WebTestCase):
  203. csrf_checks = False
  204. def test_formset_with_removed_line(self):
  205. products = [create_product() for i in range(3)]
  206. basket = factories.create_basket(empty=True)
  207. basket.owner = self.user
  208. basket.save()
  209. add_product(basket, product=products[0])
  210. add_product(basket, product=products[1])
  211. add_product(basket, product=products[2])
  212. response = self.get(reverse('basket:summary'))
  213. formset = response.context['formset']
  214. self.assertEqual(len(formset.forms), 3)
  215. basket.lines.filter(product=products[0]).delete()
  216. management_form = formset.management_form
  217. data = {
  218. formset.add_prefix('INITIAL_FORMS'): management_form.initial['INITIAL_FORMS'],
  219. formset.add_prefix('MAX_NUM_FORMS'): management_form.initial['MAX_NUM_FORMS'],
  220. formset.add_prefix('TOTAL_FORMS'): management_form.initial['TOTAL_FORMS'],
  221. 'form-0-quantity': 1,
  222. 'form-0-id': formset.forms[0].instance.id,
  223. 'form-1-quantity': 2,
  224. 'form-1-id': formset.forms[1].instance.id,
  225. 'form-2-quantity': 2,
  226. 'form-2-id': formset.forms[2].instance.id,
  227. }
  228. response = self.post(reverse('basket:summary'), params=data)
  229. self.assertEqual(response.status_code, 302)
  230. formset = response.follow().context['formset']
  231. self.assertEqual(len(formset.forms), 2)
  232. self.assertEqual(len(formset.forms_with_instances), 2)
  233. self.assertEqual(basket.lines.all()[0].quantity, 2)
  234. self.assertEqual(basket.lines.all()[1].quantity, 2)
  235. def test_invalid_formset_with_removed_line(self):
  236. products = [create_product() for i in range(3)]
  237. basket = factories.create_basket(empty=True)
  238. basket.owner = self.user
  239. basket.save()
  240. add_product(basket, product=products[0])
  241. add_product(basket, product=products[1])
  242. add_product(basket, product=products[2])
  243. response = self.get(reverse('basket:summary'))
  244. formset = response.context['formset']
  245. self.assertEqual(len(formset.forms), 3)
  246. basket.lines.filter(product=products[0]).delete()
  247. stockrecord = products[1].stockrecords.first()
  248. stockrecord.num_in_stock = 0
  249. stockrecord.save()
  250. management_form = formset.management_form
  251. data = {
  252. formset.add_prefix('INITIAL_FORMS'): management_form.initial['INITIAL_FORMS'],
  253. formset.add_prefix('MIN_NUM_FORMS'): management_form.initial['MIN_NUM_FORMS'],
  254. formset.add_prefix('MAX_NUM_FORMS'): management_form.initial['MAX_NUM_FORMS'],
  255. formset.add_prefix('TOTAL_FORMS'): management_form.initial['TOTAL_FORMS'],
  256. 'form-0-quantity': 1,
  257. 'form-0-id': formset.forms[0].instance.id,
  258. 'form-1-quantity': 2,
  259. 'form-1-id': formset.forms[1].instance.id,
  260. 'form-2-quantity': 2,
  261. 'form-2-id': formset.forms[2].instance.id,
  262. }
  263. response = self.post(reverse('basket:summary'), params=data)
  264. self.assertEqual(response.status_code, 200)
  265. formset = response.context['formset']
  266. self.assertEqual(len(formset.forms), 2)
  267. self.assertEqual(len(formset.forms_with_instances), 2)
  268. self.assertEqual(basket.lines.all()[0].quantity, 1)
  269. self.assertEqual(basket.lines.all()[1].quantity, 1)
  270. def test_deleting_valid_line_with_other_valid_line(self):
  271. product_1 = create_product()
  272. product_2 = create_product()
  273. basket = factories.create_basket(empty=True)
  274. basket.owner = self.user
  275. basket.save()
  276. add_product(basket, product=product_1)
  277. add_product(basket, product=product_2)
  278. response = self.get(reverse('basket:summary'))
  279. formset = response.context['formset']
  280. self.assertEqual(len(formset.forms), 2)
  281. data = {
  282. formset.add_prefix('TOTAL_FORMS'): formset.management_form.initial['TOTAL_FORMS'],
  283. formset.add_prefix('INITIAL_FORMS'): formset.management_form.initial['INITIAL_FORMS'],
  284. formset.add_prefix('MIN_NUM_FORMS'): formset.management_form.initial['MIN_NUM_FORMS'],
  285. formset.add_prefix('MAX_NUM_FORMS'): formset.management_form.initial['MAX_NUM_FORMS'],
  286. formset.forms[0].add_prefix('id'): formset.forms[0].instance.pk,
  287. formset.forms[0].add_prefix('quantity'): formset.forms[0].instance.quantity,
  288. formset.forms[0].add_prefix('DELETE'): 'on',
  289. formset.forms[1].add_prefix('id'): formset.forms[1].instance.pk,
  290. formset.forms[1].add_prefix('quantity'): formset.forms[1].instance.quantity,
  291. }
  292. response = self.post(reverse('basket:summary'), params=data, xhr=True)
  293. self.assertEqual(response.status_code, 200)
  294. self.assertEqual(len(response.context['formset'].forms), 1)
  295. self.assertFalse(response.context['formset'].is_bound) # new formset is rendered
  296. self.assertEqual(basket.lines.count(), 1)
  297. self.assertEqual(basket.lines.all()[0].quantity, 1)
  298. def test_deleting_valid_line_with_other_invalid_line(self):
  299. product_1 = create_product()
  300. product_2 = create_product()
  301. basket = factories.create_basket(empty=True)
  302. basket.owner = self.user
  303. basket.save()
  304. add_product(basket, product=product_1)
  305. add_product(basket, product=product_2)
  306. response = self.get(reverse('basket:summary'))
  307. formset = response.context['formset']
  308. self.assertEqual(len(formset.forms), 2)
  309. # Render product for other line out of stock
  310. product_2.stockrecords.update(num_in_stock=0)
  311. data = {
  312. formset.add_prefix('TOTAL_FORMS'): formset.management_form.initial['TOTAL_FORMS'],
  313. formset.add_prefix('INITIAL_FORMS'): formset.management_form.initial['INITIAL_FORMS'],
  314. formset.add_prefix('MIN_NUM_FORMS'): formset.management_form.initial['MIN_NUM_FORMS'],
  315. formset.add_prefix('MAX_NUM_FORMS'): formset.management_form.initial['MAX_NUM_FORMS'],
  316. formset.forms[0].add_prefix('id'): formset.forms[0].instance.pk,
  317. formset.forms[0].add_prefix('quantity'): formset.forms[0].instance.quantity,
  318. formset.forms[0].add_prefix('DELETE'): 'on',
  319. formset.forms[1].add_prefix('id'): formset.forms[1].instance.pk,
  320. formset.forms[1].add_prefix('quantity'): formset.forms[1].instance.quantity,
  321. }
  322. response = self.post(reverse('basket:summary'), params=data, xhr=True)
  323. self.assertEqual(response.status_code, 200)
  324. self.assertEqual(len(response.context['formset'].forms), 1)
  325. self.assertTrue(response.context['formset'].is_bound) # formset with errors is rendered
  326. self.assertFalse(response.context['formset'].forms[0].is_valid())
  327. self.assertEqual(basket.lines.count(), 1)
  328. self.assertEqual(basket.lines.all()[0].quantity, 1)
  329. def test_deleting_invalid_line_with_other_valid_line(self):
  330. product_1 = create_product()
  331. product_2 = create_product()
  332. basket = factories.create_basket(empty=True)
  333. basket.owner = self.user
  334. basket.save()
  335. add_product(basket, product=product_1)
  336. add_product(basket, product=product_2)
  337. response = self.get(reverse('basket:summary'))
  338. formset = response.context['formset']
  339. self.assertEqual(len(formset.forms), 2)
  340. # Render product for to-be-deleted line out of stock
  341. product_1.stockrecords.update(num_in_stock=0)
  342. data = {
  343. formset.add_prefix('TOTAL_FORMS'): formset.management_form.initial['TOTAL_FORMS'],
  344. formset.add_prefix('INITIAL_FORMS'): formset.management_form.initial['INITIAL_FORMS'],
  345. formset.add_prefix('MIN_NUM_FORMS'): formset.management_form.initial['MIN_NUM_FORMS'],
  346. formset.add_prefix('MAX_NUM_FORMS'): formset.management_form.initial['MAX_NUM_FORMS'],
  347. formset.forms[0].add_prefix('id'): formset.forms[0].instance.pk,
  348. formset.forms[0].add_prefix('quantity'): formset.forms[0].instance.quantity,
  349. formset.forms[0].add_prefix('DELETE'): 'on',
  350. formset.forms[1].add_prefix('id'): formset.forms[1].instance.pk,
  351. formset.forms[1].add_prefix('quantity'): formset.forms[1].instance.quantity,
  352. }
  353. response = self.post(reverse('basket:summary'), params=data, xhr=True)
  354. self.assertEqual(response.status_code, 200)
  355. self.assertEqual(len(response.context['formset'].forms), 1)
  356. self.assertFalse(response.context['formset'].is_bound) # new formset is rendered
  357. self.assertEqual(basket.lines.count(), 1)
  358. self.assertEqual(basket.lines.all()[0].quantity, 1)
  359. def test_deleting_invalid_line_with_other_invalid_line(self):
  360. product_1 = create_product()
  361. product_2 = create_product()
  362. basket = factories.create_basket(empty=True)
  363. basket.owner = self.user
  364. basket.save()
  365. add_product(basket, product=product_1)
  366. add_product(basket, product=product_2)
  367. response = self.get(reverse('basket:summary'))
  368. formset = response.context['formset']
  369. self.assertEqual(len(formset.forms), 2)
  370. # Render products for both lines out of stock
  371. product_1.stockrecords.update(num_in_stock=0)
  372. product_2.stockrecords.update(num_in_stock=0)
  373. data = {
  374. formset.add_prefix('TOTAL_FORMS'): formset.management_form.initial['TOTAL_FORMS'],
  375. formset.add_prefix('INITIAL_FORMS'): formset.management_form.initial['INITIAL_FORMS'],
  376. formset.add_prefix('MIN_NUM_FORMS'): formset.management_form.initial['MIN_NUM_FORMS'],
  377. formset.add_prefix('MAX_NUM_FORMS'): formset.management_form.initial['MAX_NUM_FORMS'],
  378. formset.forms[0].add_prefix('id'): formset.forms[0].instance.pk,
  379. formset.forms[0].add_prefix('quantity'): formset.forms[0].instance.quantity,
  380. formset.forms[0].add_prefix('DELETE'): 'on',
  381. formset.forms[1].add_prefix('id'): formset.forms[1].instance.pk,
  382. formset.forms[1].add_prefix('quantity'): formset.forms[1].instance.quantity,
  383. }
  384. response = self.post(reverse('basket:summary'), params=data, xhr=True)
  385. self.assertEqual(response.status_code, 200)
  386. self.assertEqual(len(response.context['formset'].forms), 1)
  387. self.assertTrue(response.context['formset'].is_bound) # formset with errors is rendered
  388. self.assertFalse(response.context['formset'].forms[0].is_valid())
  389. self.assertEqual(basket.lines.count(), 1)
  390. self.assertEqual(basket.lines.all()[0].quantity, 1)