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.

test_models.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. # -*- coding: utf-8 -*-
  2. from decimal import Decimal as D
  3. from django.test import TestCase
  4. from oscar.apps.basket.models import Basket
  5. from oscar.apps.catalogue.models import Option
  6. from oscar.apps.partner import availability, prices, strategy
  7. from oscar.test import factories
  8. from oscar.test.factories import (
  9. BasketFactory, BasketLineAttributeFactory, OptionFactory, ProductFactory)
  10. class TestANewBasket(TestCase):
  11. def setUp(self):
  12. self.basket = Basket()
  13. self.basket.strategy = strategy.Default()
  14. def test_has_zero_lines(self):
  15. self.assertEqual(0, self.basket.num_lines)
  16. def test_has_zero_items(self):
  17. self.assertEqual(0, self.basket.num_items)
  18. def test_doesnt_contain_vouchers(self):
  19. self.assertFalse(self.basket.contains_a_voucher)
  20. def test_can_be_edited(self):
  21. self.assertTrue(self.basket.can_be_edited)
  22. def test_is_empty(self):
  23. self.assertTrue(self.basket.is_empty)
  24. def test_is_not_submitted(self):
  25. self.assertFalse(self.basket.is_submitted)
  26. def test_has_no_applied_offers(self):
  27. self.assertEqual({}, self.basket.applied_offers())
  28. class TestBasketLine(TestCase):
  29. def test_description(self):
  30. basket = BasketFactory()
  31. product = ProductFactory(title="A product")
  32. basket.add_product(product)
  33. line = basket.lines.first()
  34. self.assertEqual(line.description, "A product")
  35. def test_description_with_attributes(self):
  36. basket = BasketFactory()
  37. product = ProductFactory(title="A product")
  38. basket.add_product(product)
  39. line = basket.lines.first()
  40. BasketLineAttributeFactory(
  41. line=line, value='\u2603', option__name='with')
  42. self.assertEqual(line.description, "A product (with = '\u2603')")
  43. def test_create_line_reference(self):
  44. basket = BasketFactory()
  45. product = ProductFactory(title="A product")
  46. option = OptionFactory(name="product_option", code="product_option")
  47. option_product = ProductFactory(title='Asunción')
  48. options = [{'option': option, 'value': str(option_product)}]
  49. basket.add_product(product, options=options)
  50. def test_basket_lines_queryset_is_ordered(self):
  51. # This is needed to make sure a formset is not performing the query
  52. # again with an order_by clause (losing all calculated discounts)
  53. basket = BasketFactory()
  54. product = ProductFactory(title="A product")
  55. another_product = ProductFactory(title="Another product")
  56. basket.add_product(product)
  57. basket.add_product(another_product)
  58. queryset = basket.all_lines()
  59. self.assertTrue(queryset.ordered)
  60. def test_line_tax_for_zero_tax_strategies(self):
  61. basket = Basket()
  62. basket.strategy = strategy.Default()
  63. product = factories.create_product()
  64. # Tax for the default strategy will be 0
  65. factories.create_stockrecord(
  66. product, price=D('75.00'), num_in_stock=10)
  67. basket.add(product, 1)
  68. self.assertEqual(basket.lines.first().line_tax, D('0'))
  69. def test_line_tax_for_unknown_tax_strategies(self):
  70. class UnknownTaxStrategy(strategy.Default):
  71. """ A test strategy where the tax is not known """
  72. def pricing_policy(self, product, stockrecord):
  73. return prices.FixedPrice('GBP', stockrecord.price, tax=None)
  74. basket = Basket()
  75. basket.strategy = UnknownTaxStrategy()
  76. product = factories.create_product()
  77. factories.create_stockrecord(product, num_in_stock=10)
  78. basket.add(product, 1)
  79. self.assertEqual(basket.lines.first().line_tax, None)
  80. class TestAddingAProductToABasket(TestCase):
  81. def setUp(self):
  82. self.basket = Basket()
  83. self.basket.strategy = strategy.Default()
  84. self.product = factories.create_product()
  85. self.record = factories.create_stockrecord(
  86. currency='GBP',
  87. product=self.product, price=D('10.00'))
  88. self.purchase_info = factories.create_purchase_info(self.record)
  89. self.basket.add(self.product)
  90. def test_creates_a_line(self):
  91. self.assertEqual(1, self.basket.num_lines)
  92. def test_sets_line_prices(self):
  93. line = self.basket.lines.all()[0]
  94. self.assertEqual(line.price_incl_tax, self.purchase_info.price.incl_tax)
  95. self.assertEqual(line.price_excl_tax, self.purchase_info.price.excl_tax)
  96. def test_adding_negative_quantity(self):
  97. self.assertEqual(1, self.basket.num_lines)
  98. self.basket.add(self.product, quantity=4)
  99. self.assertEqual(5, self.basket.line_quantity(self.product, self.record))
  100. self.basket.add(self.product, quantity=-10)
  101. self.assertEqual(0, self.basket.line_quantity(self.product, self.record))
  102. def test_means_another_currency_product_cannot_be_added(self):
  103. product = factories.create_product()
  104. factories.create_stockrecord(
  105. currency='USD', product=product, price=D('20.00'))
  106. with self.assertRaises(ValueError):
  107. self.basket.add(product)
  108. def test_cannot_add_a_product_without_price(self):
  109. product = factories.create_product(price=None)
  110. with self.assertRaises(ValueError):
  111. self.basket.add(product)
  112. class TestANonEmptyBasket(TestCase):
  113. def setUp(self):
  114. self.basket = Basket()
  115. self.basket.strategy = strategy.Default()
  116. self.product = factories.create_product()
  117. self.record = factories.create_stockrecord(
  118. self.product, price=D('10.00'))
  119. self.purchase_info = factories.create_purchase_info(self.record)
  120. self.basket.add(self.product, 10)
  121. def test_can_be_flushed(self):
  122. self.basket.flush()
  123. self.assertEqual(self.basket.num_items, 0)
  124. def test_returns_correct_product_quantity(self):
  125. self.assertEqual(10, self.basket.product_quantity(
  126. self.product))
  127. def test_returns_correct_line_quantity_for_existing_product_and_stockrecord(self):
  128. self.assertEqual(10, self.basket.line_quantity(
  129. self.product, self.record))
  130. def test_returns_zero_line_quantity_for_alternative_stockrecord(self):
  131. record = factories.create_stockrecord(
  132. self.product, price=D('5.00'))
  133. self.assertEqual(0, self.basket.line_quantity(
  134. self.product, record))
  135. def test_returns_zero_line_quantity_for_missing_product_and_stockrecord(self):
  136. product = factories.create_product()
  137. record = factories.create_stockrecord(
  138. product, price=D('5.00'))
  139. self.assertEqual(0, self.basket.line_quantity(
  140. product, record))
  141. def test_returns_correct_quantity_for_existing_product_and_stockrecord_and_options(self):
  142. product = factories.create_product()
  143. record = factories.create_stockrecord(
  144. product, price=D('5.00'))
  145. option = Option.objects.create(name="Message")
  146. options = [{"option": option, "value": "2"}]
  147. self.basket.add(product, options=options)
  148. self.assertEqual(0, self.basket.line_quantity(
  149. product, record))
  150. self.assertEqual(1, self.basket.line_quantity(
  151. product, record, options))
  152. def test_total_sums_product_totals(self):
  153. product = factories.create_product()
  154. factories.create_stockrecord(
  155. product, price=D('5.00'))
  156. self.basket.add(product, 1)
  157. self.assertEqual(self.basket.total_excl_tax, 105)
  158. def test_totals_for_free_products(self):
  159. basket = Basket()
  160. basket.strategy = strategy.Default()
  161. # Add a zero-priced product to the basket
  162. product = factories.create_product()
  163. factories.create_stockrecord(
  164. product, price=D('0.00'), num_in_stock=10)
  165. basket.add(product, 1)
  166. self.assertEqual(basket.lines.count(), 1)
  167. self.assertEqual(basket.total_excl_tax, 0)
  168. self.assertEqual(basket.total_incl_tax, 0)
  169. def test_basket_prices_calculation_for_unavailable_pricing(self):
  170. new_product = factories.create_product()
  171. factories.create_stockrecord(
  172. new_product, price=D('5.00'))
  173. self.basket.add(new_product, 1)
  174. class UnavailableProductStrategy(strategy.Default):
  175. """ A test strategy that makes a specific product unavailable """
  176. def availability_policy(self, product, stockrecord):
  177. if product == new_product:
  178. return availability.Unavailable()
  179. return super().availability_policy(product, stockrecord)
  180. def pricing_policy(self, product, stockrecord):
  181. if product == new_product:
  182. return prices.Unavailable()
  183. return super().pricing_policy(product, stockrecord)
  184. self.basket.strategy = UnavailableProductStrategy()
  185. line = self.basket.all_lines()[1]
  186. self.assertEqual(line.get_warning(), "'D\xf9\uff4d\u03fb\u03d2 title' is no longer available")
  187. self.assertIsNone(line.line_price_excl_tax)
  188. self.assertIsNone(line.line_price_incl_tax)
  189. self.assertIsNone(line.line_price_excl_tax_incl_discounts)
  190. self.assertIsNone(line.line_price_incl_tax_incl_discounts)
  191. self.assertIsNone(line.line_tax)
  192. self.assertEqual(self.basket.total_excl_tax, 100)
  193. self.assertEqual(self.basket.total_incl_tax, 100)
  194. self.assertEqual(self.basket.total_excl_tax_excl_discounts, 100)
  195. self.assertEqual(self.basket.total_incl_tax_excl_discounts, 100)
  196. def test_max_allowed_quantity(self):
  197. self.basket.add_product(self.product, quantity=3)
  198. # max allowed here is 7 (20-10+3)
  199. with self.settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=20):
  200. max_allowed, basket_threshold = self.basket.max_allowed_quantity()
  201. self.assertEqual(max_allowed, 7)
  202. self.assertEqual(basket_threshold, 20)
  203. # but we can also completely disable the threshold
  204. with self.settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=None):
  205. max_allowed, basket_threshold = self.basket.max_allowed_quantity()
  206. self.assertEqual(max_allowed, None)
  207. self.assertEqual(basket_threshold, None)
  208. def test_is_quantity_allowed(self):
  209. with self.settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=20):
  210. # 7 or below is possible
  211. allowed, message = self.basket.is_quantity_allowed(qty=7)
  212. self.assertTrue(allowed)
  213. self.assertIsNone(message)
  214. # but above it's not
  215. allowed, message = self.basket.is_quantity_allowed(qty=11)
  216. self.assertFalse(allowed)
  217. self.assertIsNotNone(message)
  218. with self.settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=None):
  219. # with the treshold disabled all quantities are possible
  220. allowed, message = self.basket.is_quantity_allowed(qty=7)
  221. self.assertTrue(allowed)
  222. self.assertIsNone(message)
  223. allowed, message = self.basket.is_quantity_allowed(qty=5000)
  224. self.assertTrue(allowed)
  225. self.assertIsNone(message)
  226. class TestMergingTwoBaskets(TestCase):
  227. def setUp(self):
  228. self.product = factories.create_product()
  229. self.record = factories.create_stockrecord(
  230. self.product, price=D('10.00'))
  231. self.purchase_info = factories.create_purchase_info(self.record)
  232. self.main_basket = Basket()
  233. self.main_basket.strategy = strategy.Default()
  234. self.main_basket.add(self.product, quantity=2)
  235. self.merge_basket = Basket()
  236. self.merge_basket.strategy = strategy.Default()
  237. self.merge_basket.add(self.product, quantity=1)
  238. self.main_basket.merge(self.merge_basket)
  239. def test_doesnt_sum_quantities(self):
  240. self.assertEqual(1, self.main_basket.num_lines)
  241. def test_changes_status_of_merge_basket(self):
  242. self.assertEqual(Basket.MERGED, self.merge_basket.status)
  243. class TestASubmittedBasket(TestCase):
  244. def setUp(self):
  245. self.basket = Basket()
  246. self.basket.strategy = strategy.Default()
  247. self.basket.submit()
  248. def test_has_correct_status(self):
  249. self.assertTrue(self.basket.is_submitted)
  250. def test_can_be_edited(self):
  251. self.assertFalse(self.basket.can_be_edited)
  252. class TestMergingAVoucherBasket(TestCase):
  253. def test_transfers_vouchers_to_new_basket(self):
  254. baskets = [factories.BasketFactory(), factories.BasketFactory()]
  255. voucher = factories.VoucherFactory()
  256. baskets[0].vouchers.add(voucher)
  257. baskets[1].merge(baskets[0])
  258. self.assertEqual(1, baskets[1].vouchers.all().count())