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 13KB

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