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.

order_tests.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. from decimal import Decimal as D
  2. import hashlib
  3. import time
  4. from django.test import TestCase
  5. from django.conf import settings
  6. from mock import Mock
  7. from oscar.apps.address.models import Country
  8. from oscar.apps.basket.models import Basket
  9. from oscar.apps.order.models import ShippingAddress, Order, Line, \
  10. ShippingEvent, ShippingEventType, ShippingEventQuantity, OrderNote, \
  11. OrderDiscount
  12. from oscar.apps.order.exceptions import (InvalidOrderStatus, InvalidLineStatus,
  13. InvalidShippingEvent)
  14. from oscar.test.helpers import create_order, create_product, create_offer
  15. from oscar.apps.order.utils import OrderCreator
  16. from oscar.apps.order.processing import EventHandler
  17. from oscar.test import patch_settings
  18. ORDER_PLACED = 'order_placed'
  19. class ShippingAddressTest(TestCase):
  20. fixtures = ['countries.json']
  21. def test_titleless_salutation_is_stripped(self):
  22. country = Country.objects.get(iso_3166_1_a2='GB')
  23. a = ShippingAddress.objects.create(last_name='Barrington', line1="75 Smith Road", postcode="N4 8TY", country=country)
  24. self.assertEquals("Barrington", a.salutation())
  25. class OrderTest(TestCase):
  26. def setUp(self):
  27. self.order = create_order(number='100002')
  28. self.order_placed,_ = ShippingEventType.objects.get_or_create(code='order_placed',
  29. name='Order placed')
  30. def tearDown(self):
  31. ShippingEventType.objects.all().delete()
  32. def tearDown(self):
  33. Order.objects.all().delete()
  34. def event(self, type):
  35. """
  36. Creates an order-level shipping event
  37. """
  38. type = self.order_placed
  39. event = ShippingEvent.objects.create(order=self.order, event_type=type)
  40. for line in self.order.lines.all():
  41. ShippingEventQuantity.objects.create(event=event, line=line)
  42. def test_shipping_status_is_empty_with_no_events(self):
  43. self.assertEquals("", self.order.shipping_status)
  44. def test_shipping_status_after_one_order_level_events(self):
  45. self.event(ORDER_PLACED)
  46. self.assertEquals("Order placed", self.order.shipping_status)
  47. def test_order_hash_generation(self):
  48. expected = hashlib.md5("%s%s" % (self.order.number, settings.SECRET_KEY)).hexdigest()
  49. self.assertEqual(expected, self.order.verification_hash())
  50. class OrderStatusPipelineTests(TestCase):
  51. def setUp(self):
  52. Order.pipeline = {'PENDING': ('SHIPPED', 'CANCELLED'),
  53. 'SHIPPED': ('COMPLETE',)}
  54. Order.cascade = {'SHIPPED': 'SHIPPED'}
  55. def tearDown(self):
  56. Order.pipeline = {}
  57. Order.cascade = {}
  58. def test_available_statuses_for_pending(self):
  59. self.order = create_order(status='PENDING')
  60. self.assertEqual(('SHIPPED', 'CANCELLED'),
  61. self.order.available_statuses())
  62. def test_available_statuses_for_shipped_order(self):
  63. self.order = create_order(status='SHIPPED')
  64. self.assertEqual(('COMPLETE',), self.order.available_statuses())
  65. def test_no_statuses_available_for_no_status(self):
  66. self.order = create_order()
  67. self.assertEqual((), self.order.available_statuses())
  68. def test_set_status_respects_pipeline(self):
  69. self.order = create_order(status='SHIPPED')
  70. with self.assertRaises(InvalidOrderStatus):
  71. self.order.set_status('PENDING')
  72. def test_set_status_does_nothing_for_same_status(self):
  73. self.order = create_order(status='PENDING')
  74. self.order.set_status('PENDING')
  75. self.assertEqual('PENDING', self.order.status)
  76. def test_set_status_works(self):
  77. self.order = create_order(status='PENDING')
  78. self.order.set_status('SHIPPED')
  79. self.assertEqual('SHIPPED', self.order.status)
  80. def test_cascading_status_change(self):
  81. self.order = create_order(status='PENDING')
  82. self.order.set_status('SHIPPED')
  83. for line in self.order.lines.all():
  84. self.assertEqual('SHIPPED', line.status)
  85. class OrderNoteTests(TestCase):
  86. def setUp(self):
  87. self.order = create_order()
  88. def test_system_notes_are_not_editable(self):
  89. note = self.order.notes.create(note_type=OrderNote.SYSTEM, message='test')
  90. self.assertFalse(note.is_editable())
  91. def test_non_system_notes_are_editable(self):
  92. note = self.order.notes.create(message='test')
  93. self.assertTrue(note.is_editable())
  94. def test_notes_are_not_editable_after_timeout(self):
  95. OrderNote.editable_lifetime = 1
  96. note = self.order.notes.create(message='test')
  97. self.assertTrue(note.is_editable())
  98. time.sleep(2)
  99. self.assertFalse(note.is_editable())
  100. class LineTests(TestCase):
  101. def setUp(self):
  102. basket = Basket()
  103. basket.add_product(create_product(price=D('10.00')), 4)
  104. self.order = create_order(number='100002', basket=basket)
  105. self.line = self.order.lines.all()[0]
  106. self.order_placed,_ = ShippingEventType.objects.get_or_create(code='order_placed',
  107. name='Order placed')
  108. self.dispatched,_ = ShippingEventType.objects.get_or_create(code='dispatched',
  109. name='Dispatched')
  110. def tearDown(self):
  111. ShippingEventType.objects.all().delete()
  112. def event(self, type, quantity=None):
  113. """
  114. Creates a shipping event for the test line
  115. """
  116. event = ShippingEvent.objects.create(order=self.order, event_type=type)
  117. if quantity == None:
  118. quantity = self.line.quantity
  119. event_quantity = ShippingEventQuantity.objects.create(event=event, line=self.line, quantity=quantity)
  120. def test_shipping_event_history(self):
  121. self.event(self.order_placed, 3)
  122. self.event(self.dispatched, 1)
  123. history = self.line.shipping_event_breakdown()
  124. self.assertEqual(3, history['Order placed']['quantity'])
  125. self.assertEqual(1, history['Dispatched']['quantity'])
  126. def test_shipping_status_is_empty_to_start_with(self):
  127. self.assertEquals('', self.line.shipping_status)
  128. def test_shipping_status_after_full_line_event(self):
  129. self.event(self.order_placed)
  130. self.assertEquals(self.order_placed.name, self.line.shipping_status)
  131. def test_shipping_status_after_two_full_line_events(self):
  132. type1 = self.order_placed
  133. self.event(type1)
  134. type2 = self.dispatched
  135. self.event(type2)
  136. self.assertEquals(type2.name, self.line.shipping_status)
  137. def test_shipping_status_after_partial_line_event(self):
  138. type = self.order_placed
  139. self.event(type, 3)
  140. expected = "%s (%d/%d items)" % (type.name, 3, self.line.quantity)
  141. self.assertEquals(expected, self.line.shipping_status)
  142. def test_has_passed_shipping_status_after_full_line_event(self):
  143. type = self.order_placed
  144. self.event(type)
  145. self.assertTrue(self.line.has_shipping_event_occurred(type))
  146. def test_has_passed_shipping_status_after_partial_line_event(self):
  147. type = self.order_placed
  148. self.event(type, self.line.quantity - 1)
  149. self.assertFalse(self.line.has_shipping_event_occurred(type), 1)
  150. def test_has_passed_shipping_status_after_multiple_line_event(self):
  151. event_types = [ShippingEventType.objects.get(code='order_placed'),
  152. ShippingEventType.objects.get(code='dispatched')]
  153. for type in event_types:
  154. self.event(type)
  155. for type in event_types:
  156. self.assertTrue(self.line.has_shipping_event_occurred(type))
  157. def test_inconsistent_shipping_status_setting(self):
  158. type = self.order_placed
  159. self.event(type, self.line.quantity - 1)
  160. with self.assertRaises(ValueError):
  161. self.event(type, self.line.quantity)
  162. def test_inconsistent_shipping_quantities(self):
  163. type = ShippingEventType.objects.get(code='order_placed')
  164. self.event(type, self.line.quantity - 1)
  165. with self.assertRaises(ValueError):
  166. # Total quantity is too high
  167. self.event(type, 2)
  168. class LineStatusTests(TestCase):
  169. def setUp(self):
  170. Line.pipeline = {'A': ('B', 'C'),
  171. 'B': ('C',)}
  172. self.order = create_order()
  173. self.line = self.order.lines.all()[0]
  174. self.line.status = 'A'
  175. self.line.save()
  176. def test_all_statuses_class_method(self):
  177. self.assertEqual(['A', 'B'], Line.all_statuses())
  178. def test_invalid_status_set_raises_exception(self):
  179. with self.assertRaises(InvalidLineStatus):
  180. self.line.set_status('D')
  181. def test_set_status_changes_status(self):
  182. self.line.set_status('C')
  183. self.assertEqual('C', self.line.status)
  184. def test_setting_same_status_does_nothing(self):
  185. self.line.set_status('A')
  186. class ShippingEventTypeTests(TestCase):
  187. def tearDown(self):
  188. ShippingEventType.objects.all().delete()
  189. def test_code_is_set_automatically(self):
  190. etype = ShippingEventType.objects.create(name='Returned')
  191. self.assertEqual('returned', etype.code)
  192. def test_get_prerequisites(self):
  193. ShippingEventType.objects.create(name='Shipped',
  194. is_required=True,
  195. sequence_number=0)
  196. etype = ShippingEventType.objects.create(name='Returned',
  197. is_required=False,
  198. sequence_number=1)
  199. prereqs = etype.get_prerequisites()
  200. self.assertEqual(1, len(prereqs))
  201. self.assertEqual('Shipped', prereqs[0].name)
  202. class ShippingEventQuantityTests(TestCase):
  203. def setUp(self):
  204. basket = Basket()
  205. basket.add_product(create_product(price=D('10.00')), 4)
  206. self.order = create_order(number='100002', basket=basket)
  207. self.line = self.order.lines.all()[0]
  208. self.shipped,_ = ShippingEventType.objects.get_or_create(name='Shipped',
  209. is_required=True,
  210. sequence_number=0)
  211. self.returned,_ = ShippingEventType.objects.get_or_create(name='Returned',
  212. is_required=False,
  213. sequence_number=1)
  214. def tearDown(self):
  215. ShippingEventType.objects.all().delete()
  216. def test_quantity_defaults_to_all(self):
  217. event = self.order.shipping_events.create(event_type=self.shipped)
  218. event_quantity = ShippingEventQuantity.objects.create(event=event, line=self.line)
  219. self.assertEquals(self.line.quantity, event_quantity.quantity)
  220. def test_exception_is_raised_if_previous_events_are_not_passed(self):
  221. event = self.order.shipping_events.create(event_type=self.returned)
  222. with self.assertRaises(InvalidShippingEvent):
  223. ShippingEventQuantity.objects.create(event=event,
  224. line=self.line)
  225. def test_event_is_created_ok_when_prerequisites_are_met(self):
  226. shipped_event = self.order.shipping_events.create(event_type=self.shipped)
  227. ShippingEventQuantity.objects.create(event=shipped_event,
  228. line=self.line)
  229. event = self.order.shipping_events.create(event_type=self.returned)
  230. ShippingEventQuantity.objects.create(event=event,
  231. line=self.line,
  232. quantity=1)
  233. class OrderCreatorTests(TestCase):
  234. def setUp(self):
  235. self.creator = OrderCreator()
  236. self.basket = Basket.objects.create()
  237. def tearDown(self):
  238. Order.objects.all().delete()
  239. def test_exception_raised_when_empty_basket_passed(self):
  240. with self.assertRaises(ValueError):
  241. self.creator.place_order(basket=self.basket)
  242. def test_order_models_are_created(self):
  243. self.basket.add_product(create_product(price=D('12.00')))
  244. self.creator.place_order(basket=self.basket, order_number='1234')
  245. order = Order.objects.get(number='1234')
  246. lines = order.lines.all()
  247. self.assertEqual(1, len(lines))
  248. def test_status_is_saved_if_passed(self):
  249. self.basket.add_product(create_product(price=D('12.00')))
  250. self.creator.place_order(basket=self.basket, order_number='1234', status='Active')
  251. order = Order.objects.get(number='1234')
  252. self.assertEqual('Active', order.status)
  253. def test_shipping_is_free_by_default(self):
  254. self.basket.add_product(create_product(price=D('12.00')))
  255. self.creator.place_order(basket=self.basket, order_number='1234')
  256. order = Order.objects.get(number='1234')
  257. self.assertEqual(order.total_incl_tax, self.basket.total_incl_tax)
  258. self.assertEqual(order.total_excl_tax, self.basket.total_excl_tax)
  259. def test_basket_totals_are_used_by_default(self):
  260. self.basket.add_product(create_product(price=D('12.00')))
  261. method = Mock()
  262. method.basket_charge_incl_tax = Mock(return_value=D('2.00'))
  263. method.basket_charge_excl_tax = Mock(return_value=D('2.00'))
  264. self.creator.place_order(basket=self.basket, order_number='1234', shipping_method=method)
  265. order = Order.objects.get(number='1234')
  266. self.assertEqual(order.total_incl_tax, self.basket.total_incl_tax + D('2.00'))
  267. self.assertEqual(order.total_excl_tax, self.basket.total_excl_tax + D('2.00'))
  268. def test_exception_raised_if_duplicate_number_passed(self):
  269. self.basket.add_product(create_product(price=D('12.00')))
  270. self.creator.place_order(basket=self.basket, order_number='1234')
  271. with self.assertRaises(ValueError):
  272. self.creator.place_order(basket=self.basket, order_number='1234')
  273. def test_default_order_status_comes_from_settings(self):
  274. self.basket.add_product(create_product(price=D('12.00')))
  275. with patch_settings(OSCAR_INITIAL_ORDER_STATUS='A'):
  276. self.creator.place_order(basket=self.basket, order_number='1234')
  277. order = Order.objects.get(number='1234')
  278. self.assertEqual('A', order.status)
  279. def test_default_line_status_comes_from_settings(self):
  280. self.basket.add_product(create_product(price=D('12.00')))
  281. with patch_settings(OSCAR_INITIAL_LINE_STATUS='A'):
  282. self.creator.place_order(basket=self.basket, order_number='1234')
  283. order = Order.objects.get(number='1234')
  284. line = order.lines.all()[0]
  285. self.assertEqual('A', line.status)
  286. class EventHandlerTests(TestCase):
  287. def setUp(self):
  288. self.order = create_order()
  289. self.handler = EventHandler()
  290. self.shipped = ShippingEventType.objects.create(name='Shipped')
  291. self.returned = ShippingEventType.objects.create(name='Returned')
  292. def test_shipping_handler_creates_event(self):
  293. self.handler.handle_shipping_event(self.order, self.shipped,
  294. self.order.lines.all(), [1])
  295. events = self.order.shipping_events.all()
  296. self.assertEqual(1, len(events))
  297. event = events[0]
  298. self.assertEqual('Shipped', event.event_type.name)
  299. class OrderDiscountTests(TestCase):
  300. def test_creation_without_offer_or_voucher(self):
  301. order = create_order(number='100002')
  302. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'))
  303. self.assertTrue(discount.voucher is None)
  304. self.assertTrue(discount.offer is None)
  305. def test_creation_with_offer(self):
  306. offer = create_offer()
  307. order = create_order(number='100002')
  308. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  309. offer_id=offer.id)
  310. self.assertEqual(offer.id, discount.offer.id)