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.

model_tests.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. from datetime import timedelta, datetime
  2. from decimal import Decimal as D
  3. from django.test import TestCase
  4. from django.utils import timezone
  5. from django.utils.translation import ugettext_lazy as _
  6. import mock
  7. from oscar.apps.order.exceptions import (
  8. InvalidOrderStatus, InvalidLineStatus, InvalidShippingEvent)
  9. from oscar.apps.order.models import (
  10. Order, Line, ShippingEvent, ShippingEventType, ShippingEventQuantity,
  11. OrderNote, OrderDiscount)
  12. from oscar.test.basket import add_product
  13. from oscar.test.factories import (
  14. create_order, create_offer, create_voucher, create_basket,
  15. OrderFactory, OrderLineFactory, ShippingAddressFactory,
  16. ShippingEventFactory)
  17. ORDER_PLACED = 'order_placed'
  18. class ShippingAddressTest(TestCase):
  19. def test_titleless_salutation_is_stripped(self):
  20. a = ShippingAddressFactory(
  21. first_name='', last_name='Barrington', line1="75 Smith Road",
  22. postcode="N4 8TY")
  23. self.assertEqual("Barrington", a.salutation)
  24. class OrderStatusPipelineTests(TestCase):
  25. def setUp(self):
  26. Order.pipeline = {'PENDING': ('SHIPPED', 'CANCELLED'),
  27. 'SHIPPED': ('COMPLETE',)}
  28. Order.cascade = {'SHIPPED': 'SHIPPED'}
  29. def tearDown(self):
  30. Order.pipeline = {}
  31. Order.cascade = {}
  32. def test_available_statuses_for_pending(self):
  33. self.order = create_order(status='PENDING')
  34. self.assertEqual(('SHIPPED', 'CANCELLED'),
  35. self.order.available_statuses())
  36. def test_available_statuses_for_shipped_order(self):
  37. self.order = create_order(status='SHIPPED')
  38. self.assertEqual(('COMPLETE',), self.order.available_statuses())
  39. def test_no_statuses_available_for_no_status(self):
  40. self.order = create_order()
  41. self.assertEqual((), self.order.available_statuses())
  42. def test_set_status_respects_pipeline(self):
  43. self.order = create_order(status='SHIPPED')
  44. with self.assertRaises(InvalidOrderStatus):
  45. self.order.set_status('PENDING')
  46. def test_set_status_does_nothing_for_same_status(self):
  47. self.order = create_order(status='PENDING')
  48. self.order.set_status('PENDING')
  49. self.assertEqual('PENDING', self.order.status)
  50. def test_set_status_works(self):
  51. self.order = create_order(status='PENDING')
  52. self.order.set_status('SHIPPED')
  53. self.assertEqual('SHIPPED', self.order.status)
  54. def test_cascading_status_change(self):
  55. self.order = create_order(status='PENDING')
  56. self.order.set_status('SHIPPED')
  57. for line in self.order.lines.all():
  58. self.assertEqual('SHIPPED', line.status)
  59. class OrderNoteTests(TestCase):
  60. def setUp(self):
  61. self.order = create_order()
  62. def test_system_notes_are_not_editable(self):
  63. note = self.order.notes.create(note_type=OrderNote.SYSTEM, message='test')
  64. self.assertFalse(note.is_editable())
  65. def test_non_system_notes_are_editable(self):
  66. note = self.order.notes.create(message='test')
  67. self.assertTrue(note.is_editable())
  68. def test_notes_are_not_editable_after_timeout(self):
  69. OrderNote.editable_lifetime = 1
  70. note = self.order.notes.create(message='test')
  71. self.assertTrue(note.is_editable())
  72. now = timezone.now()
  73. with mock.patch.object(timezone, 'now') as mock_timezone:
  74. mock_timezone.return_value = now + timedelta(seconds=30)
  75. self.assertFalse(note.is_editable())
  76. class LineTests(TestCase):
  77. def setUp(self):
  78. basket = create_basket(empty=True)
  79. add_product(basket, D('10.00'), 4)
  80. self.order = create_order(number='100002', basket=basket)
  81. self.line = self.order.lines.all()[0]
  82. self.order_placed, __ = ShippingEventType.objects.get_or_create(
  83. code='order_placed', name='Order placed')
  84. self.dispatched, __ = ShippingEventType.objects.get_or_create(
  85. code='dispatched', name='Dispatched')
  86. def tearDown(self):
  87. ShippingEventType.objects.all().delete()
  88. def event(self, type, quantity=None):
  89. """
  90. Creates a shipping event for the test line
  91. """
  92. event = ShippingEvent.objects.create(order=self.order, event_type=type)
  93. if quantity is None:
  94. quantity = self.line.quantity
  95. ShippingEventQuantity.objects.create(
  96. event=event, line=self.line, quantity=quantity)
  97. def test_shipping_event_history(self):
  98. self.event(self.order_placed, 3)
  99. self.event(self.dispatched, 1)
  100. history = self.line.shipping_event_breakdown
  101. self.assertEqual(3, history['Order placed']['quantity'])
  102. self.assertEqual(1, history['Dispatched']['quantity'])
  103. def test_shipping_status_is_empty_to_start_with(self):
  104. self.assertEqual('', self.line.shipping_status)
  105. def test_shipping_status_after_full_line_event(self):
  106. self.event(self.order_placed)
  107. self.assertEqual(self.order_placed.name, self.line.shipping_status)
  108. def test_shipping_status_after_two_full_line_events(self):
  109. type1 = self.order_placed
  110. self.event(type1)
  111. type2 = self.dispatched
  112. self.event(type2)
  113. self.assertEqual(type2.name, self.line.shipping_status)
  114. def test_shipping_status_after_partial_line_event(self):
  115. type = self.order_placed
  116. self.event(type, 3)
  117. expected = "%s (%d/%d items)" % (type.name, 3, self.line.quantity)
  118. self.assertEqual(expected, self.line.shipping_status)
  119. def test_has_passed_shipping_status_after_full_line_event(self):
  120. type = self.order_placed
  121. self.event(type)
  122. self.assertTrue(self.line.has_shipping_event_occurred(type))
  123. def test_has_passed_shipping_status_after_partial_line_event(self):
  124. type = self.order_placed
  125. self.event(type, self.line.quantity - 1)
  126. self.assertFalse(self.line.has_shipping_event_occurred(type), 1)
  127. def test_has_passed_shipping_status_after_multiple_line_event(self):
  128. event_types = [ShippingEventType.objects.get(code='order_placed'),
  129. ShippingEventType.objects.get(code='dispatched')]
  130. for type in event_types:
  131. self.event(type)
  132. for type in event_types:
  133. self.assertTrue(self.line.has_shipping_event_occurred(type))
  134. def test_inconsistent_shipping_status_setting(self):
  135. type = self.order_placed
  136. self.event(type, self.line.quantity - 1)
  137. with self.assertRaises(InvalidShippingEvent):
  138. self.event(type, self.line.quantity)
  139. def test_inconsistent_shipping_quantities(self):
  140. type = ShippingEventType.objects.get(code='order_placed')
  141. self.event(type, self.line.quantity - 1)
  142. with self.assertRaises(InvalidShippingEvent):
  143. # Total quantity is too high
  144. self.event(type, 2)
  145. def test_handles_product_deletion_gracefully(self):
  146. product = self.line.product
  147. product.delete()
  148. line = Line.objects.get(pk=self.line.pk)
  149. self.assertIsNone(line.product)
  150. self.assertIsNone(line.stockrecord)
  151. self.assertEqual(product.title, line.title)
  152. self.assertEqual(product.upc, line.upc)
  153. class LineStatusTests(TestCase):
  154. def setUp(self):
  155. Line.pipeline = {'A': ('B', 'C'),
  156. 'B': ('C',)}
  157. self.order = create_order()
  158. self.line = self.order.lines.all()[0]
  159. self.line.status = 'A'
  160. self.line.save()
  161. def test_all_statuses_class_method(self):
  162. self.assertEqual(['A', 'B'], sorted(Line.all_statuses()))
  163. def test_invalid_status_set_raises_exception(self):
  164. with self.assertRaises(InvalidLineStatus):
  165. self.line.set_status('D')
  166. def test_set_status_changes_status(self):
  167. self.line.set_status('C')
  168. self.assertEqual('C', self.line.status)
  169. def test_setting_same_status_does_nothing(self):
  170. self.line.set_status('A')
  171. class ShippingEventTypeTests(TestCase):
  172. def tearDown(self):
  173. ShippingEventType.objects.all().delete()
  174. def test_code_is_set_automatically(self):
  175. etype = ShippingEventType.objects.create(name='Returned')
  176. self.assertEqual('returned', etype.code)
  177. class ShippingEventQuantityTests(TestCase):
  178. def setUp(self):
  179. basket = create_basket(empty=True)
  180. add_product(basket, D('10.00'), 4)
  181. self.order = create_order(number='100002', basket=basket)
  182. self.line = self.order.lines.all()[0]
  183. self.shipped, __ = ShippingEventType.objects.get_or_create(
  184. name='Shipped')
  185. self.returned, __ = ShippingEventType.objects.get_or_create(
  186. name='Returned')
  187. def tearDown(self):
  188. ShippingEventType.objects.all().delete()
  189. def test_quantity_defaults_to_all(self):
  190. event = self.order.shipping_events.create(event_type=self.shipped)
  191. event_quantity = ShippingEventQuantity.objects.create(event=event, line=self.line)
  192. self.assertEqual(self.line.quantity, event_quantity.quantity)
  193. def test_event_is_created_ok_when_prerequisites_are_met(self):
  194. shipped_event = self.order.shipping_events.create(event_type=self.shipped)
  195. ShippingEventQuantity.objects.create(event=shipped_event,
  196. line=self.line)
  197. event = self.order.shipping_events.create(event_type=self.returned)
  198. ShippingEventQuantity.objects.create(event=event,
  199. line=self.line,
  200. quantity=1)
  201. class TestOrderDiscount(TestCase):
  202. def test_can_be_created_without_offer_or_voucher(self):
  203. order = create_order(number='100002')
  204. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'))
  205. self.assertTrue(discount.voucher is None)
  206. self.assertTrue(discount.offer is None)
  207. self.assertEqual(discount.description(), u'')
  208. def test_can_be_created_with_an_offer(self):
  209. offer = create_offer()
  210. order = create_order(number='100002')
  211. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  212. offer_id=offer.id)
  213. self.assertEqual(offer.id, discount.offer.id)
  214. self.assertEqual(offer.name, discount.offer_name)
  215. def test_can_be_created_with_an_offer_and_specified_offer_name(self):
  216. offer = create_offer(name="My offer")
  217. order = create_order(number='100002')
  218. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  219. offer_id=offer.id,
  220. offer_name="Your offer")
  221. self.assertEqual(offer.id, discount.offer.id)
  222. self.assertEqual("Your offer", discount.offer_name)
  223. def test_can_be_created_with_a_voucher(self):
  224. voucher = create_voucher()
  225. order = create_order(number='100002')
  226. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  227. voucher_id=voucher.id)
  228. self.assertEqual(voucher.id, discount.voucher.id)
  229. self.assertEqual(voucher.code, discount.voucher_code)
  230. def test_can_be_created_with_a_voucher_and_specidied_voucher_code(self):
  231. voucher = create_voucher()
  232. order = create_order(number='100002')
  233. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  234. voucher_id=voucher.id,
  235. voucher_code="anothercode")
  236. self.assertEqual(voucher.id, discount.voucher.id)
  237. self.assertEqual('anothercode', discount.voucher_code)
  238. def test_contains_offer_details_after_offer_is_deleted(self):
  239. offer = create_offer(name="Get 200% off")
  240. order = create_order(number='100002')
  241. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  242. offer_id=offer.id)
  243. offer.delete()
  244. self.assertTrue(discount.voucher is None)
  245. self.assertTrue(discount.offer is None)
  246. self.assertEqual(discount.description(), u'Get 200% off')
  247. def test_contains_voucher_details_after_voucher_is_deleted(self):
  248. voucher = create_voucher()
  249. order = create_order(number='100002')
  250. discount = OrderDiscount.objects.create(order=order, amount=D('10.00'),
  251. voucher_id=voucher.id)
  252. voucher.delete()
  253. self.assertTrue(discount.voucher is None)
  254. self.assertTrue(discount.offer is None)
  255. self.assertEqual(discount.description(), voucher.code)
  256. class OrderTests(TestCase):
  257. def get_date_tuple(self, date=None):
  258. """
  259. Returns a tuple like (year, month, day, hour, minute) for
  260. datetime comparisons.
  261. We probably don't want to assert datetime objects have the same
  262. number of miliseconds etc. just in case the object in the test
  263. differs by some insignificant amount.
  264. """
  265. if date is None:
  266. date = timezone.now()
  267. return date.timetuple()[:-4]
  268. def test_sets_date_placed_to_now_by_default(self):
  269. order = create_order(number='100003')
  270. self.assertTupleEqual(self.get_date_tuple(order.date_placed),
  271. self.get_date_tuple())
  272. def test_allows_date_placed_to_be_changed_and_set_explicitly(self):
  273. order = create_order(number='100003')
  274. tzinfo = timezone.get_current_timezone()
  275. order.date_placed = datetime(2012, 8, 11, 16, 14, tzinfo=tzinfo)
  276. order.save()
  277. self.assertTupleEqual(self.get_date_tuple(order.date_placed),
  278. (2012, 8, 11, 16, 14))
  279. def test_shipping_status(self):
  280. order = OrderFactory()
  281. line_1 = OrderLineFactory(
  282. order=order, partner_sku='SKU1234', quantity=2)
  283. line_2 = OrderLineFactory(
  284. order=order, partner_sku='SKU5678', quantity=1)
  285. self.assertEqual(order.shipping_status, '')
  286. event_1 = ShippingEventFactory(order=order, event_type__name='Shipped')
  287. event_2 = ShippingEventFactory(order=order, event_type__name='Returned')
  288. # Default status
  289. self.assertEqual(order.shipping_status, _('In progress'))
  290. # Set first line to shipped
  291. event_1.line_quantities.create(line=line_1, quantity=2)
  292. self.assertEqual(order.shipping_status, _('In progress'))
  293. # Set first line to returned
  294. event_2.line_quantities.create(line=line_1, quantity=2)
  295. self.assertEqual(order.shipping_status, _('In progress'))
  296. # Set second line to shipped
  297. event_1.line_quantities.create(line=line_2, quantity=1)
  298. self.assertEqual(order.shipping_status, _('Shipped'))
  299. # Set second line to returned
  300. event_2.line_quantities.create(line=line_2, quantity=1)
  301. self.assertEqual(order.shipping_status, _('Returned'))