Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

test_models.py 15KB

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