Coverage for website/sales/tests/test_models.py: 100.00%
259 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from unittest.mock import MagicMock
3from django.core.exceptions import ValidationError
4from django.test import TestCase
5from django.utils import timezone
7from freezegun import freeze_time
9from activemembers.models import Committee, MemberGroupMembership
10from members.models import Member
11from payments.exceptions import PaymentError
12from payments.models import Payment
13from payments.services import create_payment
14from sales.models.order import Order, OrderItem
15from sales.models.product import Product, ProductList
16from sales.models.shift import Shift
17from sales.services import is_manager
20class ProductTest(TestCase):
21 @classmethod
22 def setUpTestData(cls):
23 cls.beer = Product.objects.create(name="beer", age_restricted=True)
24 cls.wine = Product.objects.create(name="wine", age_restricted=True)
25 cls.soda = Product.objects.create(name="soda", age_restricted=False)
27 def test_str(self):
28 self.assertEqual("beer", str(self.beer))
29 self.assertEqual("wine", str(self.wine))
30 self.assertEqual("soda", str(self.soda))
33class ProductListTest(TestCase):
34 fixtures = ["members.json", "products.json"]
36 @classmethod
37 def setUpTestData(cls):
38 cls.member = Member.objects.filter(last_name="Wiggers").first()
40 cls.beer = Product.objects.get(name="beer")
41 cls.wine = Product.objects.get(name="wine")
42 cls.soda = Product.objects.get(name="soda")
44 cls.normal = ProductList.objects.get(
45 name="normal",
46 )
47 cls.free = ProductList.objects.get(
48 name="free",
49 )
51 def test_str(self):
52 self.assertEqual("normal", str(self.normal))
53 self.assertEqual("free", str(self.free))
56@freeze_time("2021-01-01")
57class OrderTest(TestCase):
58 fixtures = ["members.json", "bank_accounts.json", "products.json"]
60 @classmethod
61 def setUpTestData(cls):
62 cls.member = Member.objects.filter(last_name="Wiggers").first()
64 cls.beer = Product.objects.get(name="beer")
65 cls.wine = Product.objects.get(name="wine")
66 cls.soda = Product.objects.get(name="soda")
68 cls.normal = ProductList.objects.get(
69 name="normal",
70 )
71 cls.free = ProductList.objects.get(
72 name="free",
73 )
75 cls.shift = Shift.objects.create(
76 start=timezone.now(),
77 end=timezone.now() + timezone.timedelta(hours=1),
78 product_list=cls.normal,
79 )
81 def test_age_restricted(self):
82 order = Order.objects.create(shift=self.shift)
83 self.assertFalse(order.age_restricted)
84 i1 = OrderItem.objects.create(
85 order=order,
86 product=self.shift.product_list.product_items.get(product=self.soda),
87 amount=1,
88 )
89 self.assertFalse(order.age_restricted)
90 i2 = OrderItem.objects.create(
91 order=order,
92 product=self.shift.product_list.product_items.get(product=self.beer),
93 amount=1,
94 )
95 self.assertTrue(order.age_restricted)
96 i2.delete()
97 self.assertFalse(order.age_restricted)
99 def test_subtotal(self):
100 order = Order.objects.create(shift=self.shift)
101 self.assertEqual(order.subtotal, 0)
102 i1 = OrderItem.objects.create(
103 order=order,
104 product=self.shift.product_list.product_items.get(product=self.soda),
105 amount=2,
106 )
107 self.assertEqual(order.subtotal, 0)
108 i2 = OrderItem.objects.create(
109 order=order,
110 product=self.shift.product_list.product_items.get(product=self.beer),
111 amount=2,
112 )
113 self.assertEqual(order.subtotal, 1)
114 i3 = OrderItem.objects.create(
115 order=order,
116 product=self.shift.product_list.product_items.get(product=self.wine),
117 amount=3,
118 )
119 self.assertEqual(order.subtotal, 2.5)
120 i2.delete()
121 self.assertEqual(order.subtotal, 1.5)
122 i3.total = 4
123 i3.save()
124 self.assertEqual(order.subtotal, 4)
126 def test_total_amount(self):
127 order = Order.objects.create(shift=self.shift)
128 self.assertEqual(order.total_amount, 0)
129 i1 = OrderItem.objects.create(
130 order=order,
131 product=self.shift.product_list.product_items.get(product=self.soda),
132 amount=2,
133 )
134 self.assertEqual(order.total_amount, 0)
135 i2 = OrderItem.objects.create(
136 order=order,
137 product=self.shift.product_list.product_items.get(product=self.beer),
138 amount=2,
139 )
140 self.assertEqual(order.total_amount, 1)
141 i3 = OrderItem.objects.create(
142 order=order,
143 product=self.shift.product_list.product_items.get(product=self.wine),
144 amount=3,
145 )
146 self.assertEqual(order.total_amount, 2.5)
147 i2.delete()
148 self.assertEqual(order.total_amount, 1.5)
149 i3.total = 4
150 i3.save()
151 self.assertEqual(order.total_amount, 4)
152 order.discount = 2
153 order.save()
154 self.assertEqual(order.total_amount, 2)
156 def test_num_items(self):
157 order = Order.objects.create(shift=self.shift)
158 self.assertEqual(order.num_items, 0)
159 i1 = OrderItem.objects.create(
160 order=order,
161 product=self.shift.product_list.product_items.get(product=self.soda),
162 amount=2,
163 )
164 self.assertEqual(order.num_items, 2)
165 i2 = OrderItem.objects.create(
166 order=order,
167 product=self.shift.product_list.product_items.get(product=self.beer),
168 amount=2,
169 )
170 self.assertEqual(order.num_items, 4)
171 i3 = OrderItem.objects.create(
172 order=order,
173 product=self.shift.product_list.product_items.get(product=self.wine),
174 amount=3,
175 )
176 self.assertEqual(order.num_items, 7)
177 i2.delete()
178 self.assertEqual(order.num_items, 5)
180 def test_create_order_shift_locked(self):
181 self.shift.locked = True
182 self.shift.save()
184 with self.assertRaises(ValueError):
185 Order.objects.create(shift=self.shift)
187 def test_create_order_shift_not_started(self):
188 self.shift.start = self.shift.start + timezone.timedelta(days=2)
189 self.shift.end = self.shift.end + timezone.timedelta(days=2)
190 self.shift.save()
192 with self.assertRaises(ValueError):
193 Order.objects.create(shift=self.shift)
195 def test_update_order_shift_locked(self):
196 order = Order.objects.create(shift=self.shift)
197 OrderItem.objects.create(
198 order=order,
199 product=self.shift.product_list.product_items.get(product=self.beer),
200 amount=2,
201 )
203 self.shift.locked = True
204 self.shift.save()
206 order.discount = 0.5
208 with self.assertRaises(Order.DoesNotExist):
209 order.refresh_from_db()
211 def test_update_order_paid(self):
212 order = Order.objects.create(shift=self.shift)
213 i1 = OrderItem.objects.create(
214 order=order,
215 product=self.shift.product_list.product_items.get(product=self.beer),
216 amount=2,
217 )
218 self.assertEqual(order.total_amount, 1)
219 self.assertEqual(i1.total, 1)
220 order.payment = create_payment(
221 order, processed_by=self.member, pay_type=Payment.CASH
222 )
223 order.save()
225 order.refresh_from_db()
226 self.assertIsNotNone(order.payment)
228 order.discount = 0.5
230 with self.assertRaises(PaymentError):
231 order.save()
233 with self.assertRaises(ValueError):
234 OrderItem.objects.create(
235 order=order,
236 product=self.shift.product_list.product_items.get(product=self.wine),
237 amount=1,
238 )
240 i1.amount = 3
241 with self.assertRaises(ValueError):
242 i1.save()
244 i1.refresh_from_db()
245 self.assertEqual(i1.amount, 2)
247 def test_discount_amount(self):
248 order = Order.objects.create(shift=self.shift)
249 i1 = OrderItem.objects.create(
250 order=order,
251 product=self.shift.product_list.product_items.get(product=self.beer),
252 amount=2,
253 )
254 order.discount = 2
255 with self.assertRaises(ValidationError):
256 order.clean()
258 order.discount = 1
259 order.clean()
260 order.save()
261 self.assertEqual(order.total_amount, 0)
263 def test_order_item_total(self):
264 order = Order.objects.create(shift=self.shift)
265 i1 = OrderItem.objects.create(
266 order=order,
267 product=self.shift.product_list.product_items.get(product=self.beer),
268 amount=2,
269 )
270 i1.refresh_from_db()
271 self.assertEqual(i1.total, 1)
273 i1.total = None
274 i1.save()
275 i1.refresh_from_db()
276 self.assertEqual(i1.total, 1)
278 i1.total = 2
279 i1.save()
280 i1.refresh_from_db()
281 self.assertEqual(i1.total, 2)
283 def test_nonexistent_product(self):
284 order = Order.objects.create(shift=self.shift)
285 i1 = OrderItem.objects.create(
286 order=order,
287 product=self.free.product_items.get(product=self.beer),
288 amount=2,
289 )
290 with self.assertRaises(ValidationError):
291 i1.clean()
294@freeze_time("2021-01-01")
295class ShiftTest(TestCase):
296 fixtures = [
297 "members.json",
298 "bank_accounts.json",
299 "member_groups.json",
300 "products.json",
301 ]
303 @classmethod
304 def setUpTestData(cls):
305 cls.member = Member.objects.filter(last_name="Wiggers").first()
307 cls.beer = Product.objects.get(name="beer")
308 cls.wine = Product.objects.get(name="wine")
309 cls.soda = Product.objects.get(name="soda")
311 cls.normal = ProductList.objects.get(
312 name="normal",
313 )
314 cls.free = ProductList.objects.get(
315 name="free",
316 )
318 cls.shift = Shift.objects.create(
319 start=timezone.now(),
320 end=timezone.now() + timezone.timedelta(hours=1),
321 product_list=cls.normal,
322 )
324 def test_time(self):
325 self.assertTrue(self.shift.active)
326 self.shift.start = self.shift.end
327 with self.assertRaises(ValidationError):
328 self.shift.clean()
330 def test_remove_orders_on_locked(self):
331 order1 = Order.objects.create(shift=self.shift)
332 order1.save()
334 order2 = Order.objects.create(shift=self.shift)
335 OrderItem.objects.create(
336 order=order2,
337 product=self.shift.product_list.product_items.get(product=self.soda),
338 amount=2,
339 )
340 order2.save()
342 order3 = Order.objects.create(shift=self.shift)
343 OrderItem.objects.create(
344 order=order3,
345 product=self.shift.product_list.product_items.get(product=self.beer),
346 amount=2,
347 )
348 order3.save()
350 order4 = Order.objects.create(shift=self.shift)
351 OrderItem.objects.create(
352 order=order4,
353 product=self.shift.product_list.product_items.get(product=self.beer),
354 amount=2,
355 )
356 order4.save()
357 order4.payment = create_payment(
358 order4, processed_by=self.member, pay_type=Payment.CASH
359 )
360 order4.save()
362 self.shift.locked = True
363 self.shift.save()
365 with self.assertRaises(Order.DoesNotExist):
366 order1.refresh_from_db()
368 order2.refresh_from_db()
370 with self.assertRaises(Order.DoesNotExist):
371 order3.refresh_from_db()
373 order4.refresh_from_db()
375 def test_active(self):
376 self.assertTrue(self.shift.active)
377 self.shift.start = self.shift.start + timezone.timedelta(minutes=10)
378 self.assertFalse(self.shift.active)
380 def test_shift_statistics(self):
381 self.assertEqual(self.shift.total_revenue, 0)
382 self.assertEqual(self.shift.total_revenue_paid, 0)
384 self.assertEqual(self.shift.num_orders, 0)
385 self.assertEqual(self.shift.num_orders_paid, 0)
387 self.assertDictEqual(self.shift.product_sales, {})
389 o1 = Order.objects.create(shift=self.shift)
390 OrderItem.objects.create(
391 order=o1,
392 product=self.shift.product_list.product_items.get(product=self.beer),
393 amount=2,
394 )
395 o2 = Order.objects.create(shift=self.shift)
396 OrderItem.objects.create(
397 order=o2,
398 product=self.shift.product_list.product_items.get(product=self.soda),
399 amount=2,
400 )
401 o3 = Order.objects.create(shift=self.shift)
402 OrderItem.objects.create(
403 order=o3,
404 product=self.shift.product_list.product_items.get(product=self.beer),
405 amount=2,
406 )
407 OrderItem.objects.create(
408 order=o3,
409 product=self.shift.product_list.product_items.get(product=self.wine),
410 amount=2,
411 )
412 o4 = Order.objects.create(shift=self.shift)
413 OrderItem.objects.create(
414 order=o4,
415 product=self.shift.product_list.product_items.get(product=self.wine),
416 amount=2,
417 )
418 o5 = Order.objects.create(shift=self.shift)
419 OrderItem.objects.create(
420 order=o5,
421 product=self.shift.product_list.product_items.get(product=self.beer),
422 amount=2,
423 )
424 OrderItem.objects.create(
425 order=o5,
426 product=self.shift.product_list.product_items.get(product=self.wine),
427 amount=2,
428 )
429 o4.payment = create_payment(o4, processed_by=self.member, pay_type=Payment.CASH)
430 o4.save()
431 o5.payment = create_payment(o5, processed_by=self.member, pay_type=Payment.CASH)
432 o5.save()
434 # The test was accidentally passing because orders had the same created at,
435 # resulting in the distinct seeing them as the same order
436 o2.created_at += timezone.timedelta(minutes=1)
437 o3.created_at += timezone.timedelta(minutes=2)
438 o4.created_at += timezone.timedelta(minutes=3)
439 o5.created_at += timezone.timedelta(minutes=4)
440 o2.save()
441 o3.save()
442 o4.save()
443 o5.save()
445 self.assertEqual(self.shift.total_revenue, 6)
446 self.assertEqual(self.shift.total_revenue_paid, 3)
448 self.assertEqual(self.shift.num_orders, 5)
449 self.assertEqual(self.shift.num_orders_paid, 3)
451 self.assertDictEqual(
452 self.shift.product_sales,
453 {self.beer.name: 6, self.soda.name: 2, self.wine.name: 6},
454 )
456 def test_is_manager(self):
457 # @todo Move this test to test_services
458 self.member.is_superuser = False
459 self.assertFalse(is_manager(self.member, self.shift))
461 cie = Committee.objects.get(pk=1)
462 MemberGroupMembership.objects.create(group=cie, member=self.member)
463 self.shift.managers.add(cie)
464 self.assertTrue(is_manager(self.member, self.shift))
466 self.shift.managers.remove(cie)
467 self.assertFalse(is_manager(self.member, self.shift))
469 self.member.is_superuser = True
470 self.assertTrue(is_manager(self.member, self.shift))
471 self.member.is_superuser = False
472 self.member.has_perm = MagicMock()
473 self.member.has_perm.return_value = True
474 self.assertTrue(is_manager(self.member, self.shift))