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.

offer_tests.py 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. from decimal import Decimal
  2. import datetime
  3. from django.conf import settings
  4. from django.test import TestCase
  5. from oscar.apps.offer.models import (Range, CountCondition, ValueCondition,
  6. CoverageCondition, ConditionalOffer,
  7. PercentageDiscountBenefit, FixedPriceBenefit,
  8. MultibuyDiscountBenefit, AbsoluteDiscountBenefit)
  9. from oscar.apps.basket.models import Basket
  10. from oscar.test.helpers import create_product
  11. class WholeSiteRangeWithGlobalBlacklistTest(TestCase):
  12. def setUp(self):
  13. self.range = Range.objects.create(name="All products", includes_all_products=True)
  14. def tearDown(self):
  15. settings.OSCAR_OFFER_BLACKLIST_PRODUCT = None
  16. def test_blacklisting_prevents_products_being_in_range(self):
  17. settings.OSCAR_OFFER_BLACKLIST_PRODUCT = lambda p: True
  18. prod = create_product()
  19. self.assertFalse(self.range.contains_product(prod))
  20. def test_blacklisting_can_use_product_class(self):
  21. settings.OSCAR_OFFER_BLACKLIST_PRODUCT = lambda p: p.product_class.name == 'giftcard'
  22. prod = create_product(product_class="giftcard")
  23. self.assertFalse(self.range.contains_product(prod))
  24. def test_blacklisting_doesnt_exlude_everything(self):
  25. settings.OSCAR_OFFER_BLACKLIST_PRODUCT = lambda p: p.product_class.name == 'giftcard'
  26. prod = create_product(product_class="book")
  27. self.assertTrue(self.range.contains_product(prod))
  28. class WholeSiteRangeTest(TestCase):
  29. def setUp(self):
  30. self.range = Range.objects.create(name="All products", includes_all_products=True)
  31. self.prod = create_product()
  32. def test_all_products_range(self):
  33. self.assertTrue(self.range.contains_product(self.prod))
  34. def test_all_products_range_with_exception(self):
  35. self.range.excluded_products.add(self.prod)
  36. self.assertFalse(self.range.contains_product(self.prod))
  37. def test_whitelisting(self):
  38. self.range.included_products.add(self.prod)
  39. self.assertTrue(self.range.contains_product(self.prod))
  40. def test_blacklisting(self):
  41. self.range.excluded_products.add(self.prod)
  42. self.assertFalse(self.range.contains_product(self.prod))
  43. class PartialRangeTest(TestCase):
  44. def setUp(self):
  45. self.range = Range.objects.create(name="All products", includes_all_products=False)
  46. self.prod = create_product()
  47. def test_empty_list(self):
  48. self.assertFalse(self.range.contains_product(self.prod))
  49. def test_included_classes(self):
  50. self.range.classes.add(self.prod.product_class)
  51. self.assertTrue(self.range.contains_product(self.prod))
  52. def test_included_class_with_exception(self):
  53. self.range.classes.add(self.prod.product_class)
  54. self.range.excluded_products.add(self.prod)
  55. self.assertFalse(self.range.contains_product(self.prod))
  56. class OfferTest(TestCase):
  57. def setUp(self):
  58. self.range = Range.objects.create(name="All products range", includes_all_products=True)
  59. self.basket = Basket.objects.create()
  60. class CountConditionTest(OfferTest):
  61. def setUp(self):
  62. super(CountConditionTest, self).setUp()
  63. self.cond = CountCondition(range=self.range, type="Count", value=2)
  64. def test_empty_basket_fails_condition(self):
  65. self.assertFalse(self.cond.is_satisfied(self.basket))
  66. def test_empty_basket_fails_partial_condition(self):
  67. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  68. def test_smaller_quantity_basket_passes_partial_condition(self):
  69. self.basket.add_product(create_product(), 1)
  70. self.assertTrue(self.cond.is_partially_satisfied(self.basket))
  71. def test_smaller_quantity_basket_upsell_message(self):
  72. self.basket.add_product(create_product(), 1)
  73. self.assertTrue('Buy 1 more product from ' in
  74. self.cond.get_upsell_message(self.basket))
  75. def test_matching_quantity_basket_fails_partial_condition(self):
  76. self.basket.add_product(create_product(), 2)
  77. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  78. def test_matching_quantity_basket_passes_condition(self):
  79. self.basket.add_product(create_product(), 2)
  80. self.assertTrue(self.cond.is_satisfied(self.basket))
  81. def test_greater_quantity_basket_passes_condition(self):
  82. self.basket.add_product(create_product(), 3)
  83. self.assertTrue(self.cond.is_satisfied(self.basket))
  84. def test_consumption(self):
  85. self.basket.add_product(create_product(), 3)
  86. self.cond.consume_items(self.basket)
  87. self.assertEquals(1, self.basket.all_lines()[0].quantity_without_discount)
  88. def test_is_satisfied_accounts_for_consumed_items(self):
  89. self.basket.add_product(create_product(), 3)
  90. self.cond.consume_items(self.basket)
  91. self.assertFalse(self.cond.is_satisfied(self.basket))
  92. def test_count_condition_is_applied_multpile_times(self):
  93. benefit = AbsoluteDiscountBenefit(range=self.range, type="Absolute", value=Decimal('10.00'))
  94. for i in range(10):
  95. self.basket.add_product(create_product(price=Decimal('5.00'), upc='upc_%i' % i), 1)
  96. product_range = Range.objects.create(name="All products", includes_all_products=True)
  97. condition = CountCondition(range=product_range, type="Count", value=2)
  98. first_discount = benefit.apply(self.basket, condition=condition)
  99. self.assertEquals(Decimal('10.00'), first_discount)
  100. second_discount = benefit.apply(self.basket, condition=condition)
  101. self.assertEquals(Decimal('10.00'), second_discount)
  102. class ValueConditionTest(OfferTest):
  103. def setUp(self):
  104. super(ValueConditionTest, self).setUp()
  105. self.cond = ValueCondition(range=self.range, type="Value", value=Decimal('10.00'))
  106. self.item = create_product(price=Decimal('5.00'))
  107. self.expensive_item = create_product(price=Decimal('15.00'))
  108. def test_empty_basket_fails_condition(self):
  109. self.assertFalse(self.cond.is_satisfied(self.basket))
  110. def test_empty_basket_fails_partial_condition(self):
  111. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  112. def test_less_value_basket_fails_condition(self):
  113. self.basket.add_product(self.item, 1)
  114. self.assertFalse(self.cond.is_satisfied(self.basket))
  115. def test_upsell_message(self):
  116. self.basket.add_product(self.item, 1)
  117. self.assertTrue('Spend' in self.cond.get_upsell_message(self.basket))
  118. def test_matching_basket_fails_partial_condition(self):
  119. self.basket.add_product(self.item, 2)
  120. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  121. def test_less_value_basket_passes_partial_condition(self):
  122. self.basket.add_product(self.item, 1)
  123. self.assertTrue(self.cond.is_partially_satisfied(self.basket))
  124. def test_matching_basket_passes_condition(self):
  125. self.basket.add_product(self.item, 2)
  126. self.assertTrue(self.cond.is_satisfied(self.basket))
  127. def test_greater_than_basket_passes_condition(self):
  128. self.basket.add_product(self.item, 3)
  129. self.assertTrue(self.cond.is_satisfied(self.basket))
  130. def test_consumption(self):
  131. self.basket.add_product(self.item, 3)
  132. self.cond.consume_items(self.basket)
  133. self.assertEquals(1, self.basket.all_lines()[0].quantity_without_discount)
  134. def test_consumption_with_high_value_product(self):
  135. self.basket.add_product(self.expensive_item, 1)
  136. self.cond.consume_items(self.basket)
  137. self.assertEquals(0, self.basket.all_lines()[0].quantity_without_discount)
  138. def test_is_consumed_respects_quantity_consumed(self):
  139. self.basket.add_product(self.expensive_item, 1)
  140. self.assertTrue(self.cond.is_satisfied(self.basket))
  141. self.cond.consume_items(self.basket)
  142. self.assertFalse(self.cond.is_satisfied(self.basket))
  143. class CoverageConditionTest(TestCase):
  144. def setUp(self):
  145. self.products = [create_product(Decimal('5.00')), create_product(Decimal('10.00'))]
  146. self.range = Range.objects.create(name="Some products")
  147. for product in self.products:
  148. self.range.included_products.add(product)
  149. self.range.included_products.add(product)
  150. self.basket = Basket.objects.create()
  151. self.cond = CoverageCondition(range=self.range, type="Coverage", value=2)
  152. def test_empty_basket_fails(self):
  153. self.assertFalse(self.cond.is_satisfied(self.basket))
  154. def test_empty_basket_fails_partial_condition(self):
  155. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  156. def test_single_item_fails(self):
  157. self.basket.add_product(self.products[0])
  158. self.assertFalse(self.cond.is_satisfied(self.basket))
  159. def test_single_item_passes_partial_condition(self):
  160. self.basket.add_product(self.products[0])
  161. self.assertTrue(self.cond.is_partially_satisfied(self.basket))
  162. def test_upsell_message(self):
  163. self.basket.add_product(self.products[0])
  164. self.assertTrue('Buy 1 more' in self.cond.get_upsell_message(self.basket))
  165. def test_duplicate_item_fails(self):
  166. self.basket.add_product(self.products[0])
  167. self.basket.add_product(self.products[0])
  168. self.assertFalse(self.cond.is_satisfied(self.basket))
  169. def test_duplicate_item_passes_partial_condition(self):
  170. self.basket.add_product(self.products[0], 2)
  171. self.assertTrue(self.cond.is_partially_satisfied(self.basket))
  172. def test_covering_items_pass(self):
  173. self.basket.add_product(self.products[0])
  174. self.basket.add_product(self.products[1])
  175. self.assertTrue(self.cond.is_satisfied(self.basket))
  176. def test_covering_items_fail_partial_condition(self):
  177. self.basket.add_product(self.products[0])
  178. self.basket.add_product(self.products[1])
  179. self.assertFalse(self.cond.is_partially_satisfied(self.basket))
  180. def test_covering_items_are_consumed(self):
  181. self.basket.add_product(self.products[0])
  182. self.basket.add_product(self.products[1])
  183. self.cond.consume_items(self.basket)
  184. self.assertEquals(0, self.basket.num_items_without_discount)
  185. def test_consumed_items_checks_affected_items(self):
  186. # Create new offer
  187. range = Range.objects.create(name="All products", includes_all_products=True)
  188. cond = CoverageCondition(range=range, type="Coverage", value=2)
  189. # Get 4 distinct products in the basket
  190. self.products.extend([create_product(Decimal('15.00')), create_product(Decimal('20.00'))])
  191. for product in self.products:
  192. self.basket.add_product(product)
  193. self.assertTrue(cond.is_satisfied(self.basket))
  194. cond.consume_items(self.basket)
  195. self.assertEquals(2, self.basket.num_items_without_discount)
  196. self.assertTrue(cond.is_satisfied(self.basket))
  197. cond.consume_items(self.basket)
  198. self.assertEquals(0, self.basket.num_items_without_discount)
  199. class PercentageDiscountBenefitTest(OfferTest):
  200. def setUp(self):
  201. super(PercentageDiscountBenefitTest, self).setUp()
  202. self.benefit = PercentageDiscountBenefit(range=self.range, type="Percentage", value=Decimal('15.00'))
  203. self.item = create_product(price=Decimal('5.00'))
  204. self.original_offer_rounding_function = getattr(settings, 'OSCAR_OFFER_ROUNDING_FUNCTION', None)
  205. if self.original_offer_rounding_function is not None:
  206. delattr(settings, 'OSCAR_OFFER_ROUNDING_FUNCTION')
  207. def tearDown(self):
  208. super(PercentageDiscountBenefitTest, self).tearDown()
  209. if self.original_offer_rounding_function is not None:
  210. settings.OSCAR_OFFER_ROUNDING_FUNCTION = self.original_offer_rounding_function
  211. def test_no_discount_for_empty_basket(self):
  212. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  213. def test_discount_for_single_item_basket(self):
  214. self.basket.add_product(self.item, 1)
  215. self.assertEquals(Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  216. def test_discount_for_multi_item_basket(self):
  217. self.basket.add_product(self.item, 3)
  218. self.assertEquals(Decimal('3') * Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  219. def test_discount_for_multi_item_basket_with_max_affected_items_set(self):
  220. self.basket.add_product(self.item, 3)
  221. self.benefit.max_affected_items = 1
  222. self.assertEquals(Decimal('0.15') * Decimal('5.00'), self.benefit.apply(self.basket))
  223. def test_discount_can_only_be_applied_once(self):
  224. self.basket.add_product(self.item, 3)
  225. first_discount = self.benefit.apply(self.basket)
  226. second_discount = self.benefit.apply(self.basket)
  227. self.assertEquals(Decimal('0.00'), second_discount)
  228. def test_discount_can_be_applied_several_times_when_max_is_set(self):
  229. self.basket.add_product(self.item, 3)
  230. self.benefit.max_affected_items = 1
  231. for i in range(1, 4):
  232. self.assertTrue(self.benefit.apply(self.basket) > 0)
  233. class AbsoluteDiscountBenefitTest(OfferTest):
  234. def setUp(self):
  235. super(AbsoluteDiscountBenefitTest, self).setUp()
  236. self.benefit = AbsoluteDiscountBenefit(range=self.range, type="Absolute", value=Decimal('10.00'))
  237. self.item = create_product(price=Decimal('5.00'))
  238. self.original_offer_rounding_function = getattr(settings, 'OSCAR_OFFER_ROUNDING_FUNCTION', None)
  239. if self.original_offer_rounding_function is not None:
  240. delattr(settings, 'OSCAR_OFFER_ROUNDING_FUNCTION')
  241. def tearDown(self):
  242. super(AbsoluteDiscountBenefitTest, self).tearDown()
  243. if self.original_offer_rounding_function is not None:
  244. settings.OSCAR_OFFER_ROUNDING_FUNCTION = self.original_offer_rounding_function
  245. def test_no_discount_for_empty_basket(self):
  246. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  247. def test_discount_for_single_item_basket(self):
  248. self.basket.add_product(self.item, 1)
  249. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  250. def test_discount_for_multi_item_basket(self):
  251. self.basket.add_product(self.item, 3)
  252. self.assertEquals(Decimal('10.00'), self.benefit.apply(self.basket))
  253. def test_discount_for_multi_item_basket_with_max_affected_items_set(self):
  254. self.basket.add_product(self.item, 3)
  255. self.benefit.max_affected_items = 1
  256. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  257. def test_discount_can_only_be_applied_once(self):
  258. # Add 3 items to make total 15.00
  259. self.basket.add_product(self.item, 3)
  260. first_discount = self.benefit.apply(self.basket)
  261. self.assertEquals(Decimal('10.00'), first_discount)
  262. second_discount = self.benefit.apply(self.basket)
  263. self.assertEquals(Decimal('5.00'), second_discount)
  264. class MultibuyDiscountBenefitTest(OfferTest):
  265. def setUp(self):
  266. super(MultibuyDiscountBenefitTest, self).setUp()
  267. self.benefit = MultibuyDiscountBenefit(range=self.range, type="Multibuy", value=1)
  268. self.item = create_product(price=Decimal('5.00'))
  269. def test_no_discount_for_empty_basket(self):
  270. self.assertEquals(Decimal('0.00'), self.benefit.apply(self.basket))
  271. def test_discount_for_single_item_basket(self):
  272. self.basket.add_product(self.item, 1)
  273. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  274. def test_discount_for_multi_item_basket(self):
  275. self.basket.add_product(self.item, 3)
  276. self.assertEquals(Decimal('5.00'), self.benefit.apply(self.basket))
  277. def test_discount_does_not_consume_item_if_in_condition_range(self):
  278. self.basket.add_product(self.item, 1)
  279. first_discount = self.benefit.apply(self.basket)
  280. self.assertEquals(Decimal('5.00'), first_discount)
  281. second_discount = self.benefit.apply(self.basket)
  282. self.assertEquals(Decimal('5.00'), second_discount)
  283. def test_product_does_consume_item_if_not_in_condition_range(self):
  284. # Set up condition using a different range from benefit
  285. range = Range.objects.create(name="Small range")
  286. other_product = create_product(price=Decimal('15.00'))
  287. range.included_products.add(other_product)
  288. cond = ValueCondition(range=range, type="Value", value=Decimal('10.00'))
  289. self.basket.add_product(self.item, 1)
  290. self.benefit.apply(self.basket, cond)
  291. line = self.basket.all_lines()[0]
  292. self.assertEqual(line.quantity_without_discount, 0)
  293. def test_condition_consumes_most_expensive_lines_first(self):
  294. for i in range(10, 0, -1):
  295. product = create_product(price=Decimal(i), title='%i'%i, upc='upc_%i' % i)
  296. self.basket.add_product(product, 1)
  297. condition = CountCondition(range=self.range, type="Count", value=2)
  298. self.assertTrue(condition.is_satisfied(self.basket))
  299. # consume 1 and 10
  300. first_discount = self.benefit.apply(self.basket, condition=condition)
  301. self.assertEquals(Decimal('1.00'), first_discount)
  302. self.assertTrue(condition.is_satisfied(self.basket))
  303. # consume 2 and 9
  304. second_discount = self.benefit.apply(self.basket, condition=condition)
  305. self.assertEquals(Decimal('2.00'), second_discount)
  306. self.assertTrue(condition.is_satisfied(self.basket))
  307. # consume 3 and 8
  308. third_discount = self.benefit.apply(self.basket, condition=condition)
  309. self.assertEquals(Decimal('3.00'), third_discount)
  310. self.assertTrue(condition.is_satisfied(self.basket))
  311. # consume 4 and 7
  312. fourth_discount = self.benefit.apply(self.basket, condition=condition)
  313. self.assertEquals(Decimal('4.00'), fourth_discount)
  314. self.assertTrue(condition.is_satisfied(self.basket))
  315. # consume 5 and 6
  316. fifth_discount = self.benefit.apply(self.basket, condition=condition)
  317. self.assertEquals(Decimal('5.00'), fifth_discount)
  318. # end of items (one not discounted item in basket)
  319. self.assertFalse(condition.is_satisfied(self.basket))
  320. def test_condition_consumes_most_expensive_lines_first_when_products_are_repeated(self):
  321. for i in range(5, 0, -1):
  322. product = create_product(price=Decimal(i), title='%i'%i, upc='upc_%i' % i)
  323. self.basket.add_product(product, 2)
  324. condition = CountCondition(range=self.range, type="Count", value=2)
  325. # initial basket: [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
  326. self.assertTrue(condition.is_satisfied(self.basket))
  327. # consume 1 and 5
  328. first_discount = self.benefit.apply(self.basket, condition=condition)
  329. self.assertEquals(Decimal('1.00'), first_discount)
  330. self.assertTrue(condition.is_satisfied(self.basket))
  331. # consume 1 and 5
  332. second_discount = self.benefit.apply(self.basket, condition=condition)
  333. self.assertEquals(Decimal('1.00'), second_discount)
  334. self.assertTrue(condition.is_satisfied(self.basket))
  335. # consume 2 and 4
  336. third_discount = self.benefit.apply(self.basket, condition=condition)
  337. self.assertEquals(Decimal('2.00'), third_discount)
  338. self.assertTrue(condition.is_satisfied(self.basket))
  339. # consume 2 and 4
  340. third_discount = self.benefit.apply(self.basket, condition=condition)
  341. self.assertEquals(Decimal('2.00'), third_discount)
  342. self.assertTrue(condition.is_satisfied(self.basket))
  343. # consume 3 and 3
  344. third_discount = self.benefit.apply(self.basket, condition=condition)
  345. self.assertEquals(Decimal('3.00'), third_discount)
  346. # end of items (one not discounted item in basket)
  347. self.assertFalse(condition.is_satisfied(self.basket))
  348. def test_products_with_no_stockrecord_are_handled_ok(self):
  349. self.basket.add_product(self.item, 3)
  350. self.basket.add_product(create_product())
  351. condition = CountCondition(range=self.range, type="Count", value=3)
  352. self.benefit.apply(self.basket, condition)
  353. class FixedPriceBenefitTest(OfferTest):
  354. def setUp(self):
  355. super(FixedPriceBenefitTest, self).setUp()
  356. self.benefit = FixedPriceBenefit(range=self.range, type="FixedPrice", value=Decimal('10.00'))
  357. def test_correct_discount_for_count_condition(self):
  358. products = [create_product(Decimal('7.00')),
  359. create_product(Decimal('8.00')),
  360. create_product(Decimal('12.00'))]
  361. # Create range that includes the products
  362. range = Range.objects.create(name="Dummy range")
  363. for product in products:
  364. range.included_products.add(product)
  365. condition = CountCondition(range=range, type="Count", value=3)
  366. # Create basket that satisfies condition but with one extra product
  367. basket = Basket.objects.create()
  368. [basket.add_product(p, 2) for p in products]
  369. benefit = FixedPriceBenefit(range=range, type="FixedPrice", value=Decimal('20.00'))
  370. self.assertEquals(Decimal('2.00'), benefit.apply(basket, condition))
  371. self.assertEquals(Decimal('12.00'), benefit.apply(basket, condition))
  372. self.assertEquals(Decimal('0.00'), benefit.apply(basket, condition))
  373. def test_correct_discount_is_returned(self):
  374. products = [create_product(Decimal('8.00')), create_product(Decimal('4.00'))]
  375. range = Range.objects.create(name="Dummy range")
  376. for product in products:
  377. range.included_products.add(product)
  378. range.included_products.add(product)
  379. basket = Basket.objects.create()
  380. [basket.add_product(p) for p in products]
  381. condition = CoverageCondition(range=range, type="Coverage", value=2)
  382. discount = self.benefit.apply(basket, condition)
  383. self.assertEquals(Decimal('2.00'), discount)
  384. def test_no_discount_is_returned_when_value_is_greater_than_product_total(self):
  385. products = [create_product(Decimal('4.00')), create_product(Decimal('4.00'))]
  386. range = Range.objects.create(name="Dummy range")
  387. for product in products:
  388. range.included_products.add(product)
  389. range.included_products.add(product)
  390. basket = Basket.objects.create()
  391. [basket.add_product(p) for p in products]
  392. condition = CoverageCondition(range=range, type="Coverage", value=2)
  393. discount = self.benefit.apply(basket, condition)
  394. self.assertEquals(Decimal('0.00'), discount)
  395. def test_discount_when_more_products_than_required(self):
  396. products = [create_product(Decimal('4.00')),
  397. create_product(Decimal('8.00')),
  398. create_product(Decimal('12.00'))]
  399. # Create range that includes the products
  400. range = Range.objects.create(name="Dummy range")
  401. for product in products:
  402. range.included_products.add(product)
  403. condition = CoverageCondition(range=range, type="Coverage", value=3)
  404. # Create basket that satisfies condition but with one extra product
  405. basket = Basket.objects.create()
  406. [basket.add_product(p) for p in products]
  407. basket.add_product(products[0])
  408. benefit = FixedPriceBenefit(range=range, type="FixedPrice", value=Decimal('20.00'))
  409. discount = benefit.apply(basket, condition)
  410. self.assertEquals(Decimal('4.00'), discount)
  411. def test_discount_when_applied_twice(self):
  412. products = [create_product(Decimal('4.00')),
  413. create_product(Decimal('8.00')),
  414. create_product(Decimal('12.00'))]
  415. # Create range that includes the products
  416. range = Range.objects.create(name="Dummy range")
  417. for product in products:
  418. range.included_products.add(product)
  419. condition = CoverageCondition(range=range, type="Coverage", value=3)
  420. # Create basket that satisfies condition but with one extra product
  421. basket = Basket.objects.create()
  422. [basket.add_product(p, 2) for p in products]
  423. benefit = FixedPriceBenefit(range=range, type="FixedPrice", value=Decimal('20.00'))
  424. first_discount = benefit.apply(basket, condition)
  425. self.assertEquals(Decimal('4.00'), first_discount)
  426. second_discount = benefit.apply(basket, condition)
  427. self.assertEquals(Decimal('4.00'), second_discount)
  428. class ConditionalOfferTest(TestCase):
  429. def test_is_active(self):
  430. start = datetime.date(2011, 01, 01)
  431. test = datetime.date(2011, 01, 10)
  432. end = datetime.date(2011, 02, 01)
  433. offer = ConditionalOffer(start_date=start, end_date=end)
  434. self.assertTrue(offer.is_active(test))
  435. def test_is_inactive(self):
  436. start = datetime.date(2011, 01, 01)
  437. test = datetime.date(2011, 03, 10)
  438. end = datetime.date(2011, 02, 01)
  439. offer = ConditionalOffer(start_date=start, end_date=end)
  440. self.assertFalse(offer.is_active(test))