All checks were successful
Build and Push Docker Image / build (push) Successful in 27s
313 lines
13 KiB
Python
313 lines
13 KiB
Python
from django.contrib.auth import get_user_model
|
||
from django.core import mail
|
||
from django.db import IntegrityError
|
||
from django.test import TestCase, override_settings
|
||
from django.urls import reverse
|
||
|
||
from items.models import Category, Item, ItemImage, Subcategory
|
||
from orders.models import Cart, CartItem, Order, OrderItem
|
||
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
@override_settings(
|
||
MEDIA_URL="/media/",
|
||
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
|
||
ADMINISTRATOR_EMAIL="admin@example.com",
|
||
)
|
||
class CartFlowTests(TestCase):
|
||
def setUp(self):
|
||
self.user = User.objects.create_user(
|
||
username="cart-user",
|
||
email="cart@example.com",
|
||
password="StrongPassword123",
|
||
is_active=True,
|
||
)
|
||
self.category = Category.objects.create(name="Сантехника", slug="plumbing")
|
||
self.subcategory = Subcategory.objects.create(name="Мойки", slug="sinks", category=self.category)
|
||
self.item = Item.objects.create(
|
||
name="Кухонная мойка",
|
||
slug="kitchen-sink",
|
||
category=self.category,
|
||
subcategory=self.subcategory,
|
||
description="Искусственный камень",
|
||
price="1600.00",
|
||
is_available=True,
|
||
)
|
||
self.unavailable_item = Item.objects.create(
|
||
name="Смеситель",
|
||
slug="tap",
|
||
category=self.category,
|
||
subcategory=self.subcategory,
|
||
description="Недоступный товар",
|
||
price="400.00",
|
||
is_available=False,
|
||
)
|
||
|
||
def checkout_data(self, **overrides):
|
||
data = {
|
||
"delivery_method": "courier",
|
||
"region": "Минск",
|
||
"address": "ул. Тимирязева, 123",
|
||
"payment_method": "card",
|
||
"customer_name": "Иван Петров",
|
||
"phone": "+375296881819",
|
||
"email": "buyer@example.com",
|
||
"recipient_matches_customer": "on",
|
||
"promo_code": "SPRING",
|
||
"terms_accepted": "on",
|
||
}
|
||
data.update(overrides)
|
||
return data
|
||
|
||
def test_cart_item_is_unique_per_cart(self):
|
||
cart = Cart.objects.create(user=self.user)
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=1)
|
||
|
||
with self.assertRaises(IntegrityError):
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=2)
|
||
|
||
def test_add_remove_and_update_quantity_for_authenticated_user(self):
|
||
self.client.force_login(self.user)
|
||
|
||
add_response = self.client.post(
|
||
reverse("orders:add"),
|
||
data={"item_id": self.item.id, "next": reverse("orders:cart")},
|
||
)
|
||
self.assertRedirects(add_response, reverse("orders:cart"))
|
||
self.assertTrue(CartItem.objects.filter(cart__user=self.user, item=self.item, quantity=1).exists())
|
||
|
||
update_response = self.client.post(
|
||
reverse("orders:update_quantity"),
|
||
data={"item_id": self.item.id, "quantity": 3, "next": reverse("orders:cart")},
|
||
)
|
||
self.assertRedirects(update_response, reverse("orders:cart"))
|
||
self.assertTrue(CartItem.objects.filter(cart__user=self.user, item=self.item, quantity=3).exists())
|
||
|
||
remove_response = self.client.post(
|
||
reverse("orders:remove"),
|
||
data={"item_id": self.item.id, "next": reverse("orders:cart")},
|
||
)
|
||
self.assertRedirects(remove_response, reverse("orders:cart"))
|
||
self.assertFalse(CartItem.objects.filter(cart__user=self.user, item=self.item).exists())
|
||
|
||
def test_add_endpoint_rejects_unavailable_item(self):
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.post(
|
||
reverse("orders:add"),
|
||
data={"item_id": self.unavailable_item.id, "next": reverse("orders:cart")},
|
||
follow=True,
|
||
)
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertFalse(CartItem.objects.filter(cart__user=self.user, item=self.unavailable_item).exists())
|
||
self.assertContains(response, "Недоступный товар нельзя добавить в корзину.")
|
||
|
||
def test_cart_page_shows_authenticated_user_items(self):
|
||
cart = Cart.objects.create(user=self.user)
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=2)
|
||
ItemImage.objects.create(
|
||
item=self.item,
|
||
image="items/cart-item.jpg",
|
||
alt="Фото мойки",
|
||
is_primary=True,
|
||
)
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.get(reverse("orders:cart"))
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertContains(response, "Оформление заказа")
|
||
self.assertContains(response, "1600,00 руб.")
|
||
self.assertContains(response, "3200,00 руб.")
|
||
self.assertContains(response, "3295,00 руб.")
|
||
self.assertContains(response, "/media/items/cart-item.jpg")
|
||
self.assertContains(
|
||
response,
|
||
'<a class="cart-item__overlay" href="/catalog/plumbing/kitchen-sink/"',
|
||
html=False,
|
||
)
|
||
self.assertContains(response, 'id="cart-increment-', html=False)
|
||
self.assertContains(response, 'action="/orders/cart/remove/"', html=False)
|
||
self.assertNotContains(response, 'name="promo_code"', html=False)
|
||
|
||
def test_cart_page_redirects_guest_to_login(self):
|
||
response = self.client.get(reverse("orders:cart"))
|
||
|
||
self.assertEqual(response.status_code, 302)
|
||
self.assertIn(reverse("users:login"), response["Location"])
|
||
|
||
def test_checkout_creates_paid_order_clears_cart_and_sends_email(self):
|
||
cart = Cart.objects.create(user=self.user)
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=2)
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.post(reverse("orders:checkout"), data=self.checkout_data())
|
||
|
||
order = Order.objects.get()
|
||
self.assertRedirects(response, reverse("orders:success", kwargs={"order_id": order.id}))
|
||
self.assertEqual(order.status, Order.Status.PAID)
|
||
self.assertEqual(order.products_total, 3200)
|
||
self.assertEqual(order.discount_total, 0)
|
||
self.assertEqual(order.delivery_total, 50)
|
||
self.assertEqual(order.floor_lift_total, 45)
|
||
self.assertEqual(order.total, 3295)
|
||
self.assertEqual(order.promo_code, "SPRING")
|
||
self.assertFalse(CartItem.objects.filter(cart=cart).exists())
|
||
|
||
order_item = OrderItem.objects.get(order=order)
|
||
self.assertEqual(order_item.item_name, "Кухонная мойка")
|
||
self.assertEqual(order_item.quantity, 2)
|
||
self.assertEqual(order_item.line_total, 3200)
|
||
|
||
self.assertEqual(len(mail.outbox), 1)
|
||
self.assertEqual(mail.outbox[0].to, ["buyer@example.com"])
|
||
self.assertEqual(mail.outbox[0].bcc, ["admin@example.com"])
|
||
self.assertIn(f"заказ №{order.id}", mail.outbox[0].subject.lower())
|
||
self.assertIn("Кухонная мойка", mail.outbox[0].body)
|
||
|
||
def test_checkout_rejects_empty_cart(self):
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.post(reverse("orders:checkout"), data=self.checkout_data())
|
||
|
||
self.assertRedirects(response, reverse("orders:cart"))
|
||
self.assertFalse(Order.objects.exists())
|
||
self.assertEqual(len(mail.outbox), 0)
|
||
|
||
def test_checkout_requires_address_for_courier_delivery(self):
|
||
cart = Cart.objects.create(user=self.user)
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=1)
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.post(
|
||
reverse("orders:checkout"),
|
||
data=self.checkout_data(region="", address=""),
|
||
)
|
||
|
||
self.assertEqual(response.status_code, 400)
|
||
self.assertContains(response, "Укажите регион доставки.", status_code=400)
|
||
self.assertContains(response, "Укажите адрес доставки.", status_code=400)
|
||
self.assertFalse(Order.objects.exists())
|
||
|
||
def test_checkout_pickup_does_not_require_address_or_add_services(self):
|
||
cart = Cart.objects.create(user=self.user)
|
||
CartItem.objects.create(cart=cart, item=self.item, quantity=1)
|
||
self.client.force_login(self.user)
|
||
|
||
response = self.client.post(
|
||
reverse("orders:checkout"),
|
||
data=self.checkout_data(delivery_method="pickup", region="", address=""),
|
||
)
|
||
|
||
order = Order.objects.get()
|
||
self.assertRedirects(response, reverse("orders:success", kwargs={"order_id": order.id}))
|
||
self.assertEqual(order.delivery_total, 0)
|
||
self.assertEqual(order.floor_lift_total, 0)
|
||
self.assertEqual(order.total, 1600)
|
||
|
||
def test_buy_now_page_is_available_to_guest(self):
|
||
response = self.client.get(reverse("orders:buy_now", kwargs={"item_id": self.item.id}))
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Оформление заказа")
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertContains(response, f'action="{reverse("orders:buy_now", kwargs={"item_id": self.item.id})}"', html=False)
|
||
self.assertNotContains(response, "cart-remove-", html=False)
|
||
|
||
def test_buy_now_rejects_unavailable_item(self):
|
||
response = self.client.get(reverse("orders:buy_now", kwargs={"item_id": self.unavailable_item.id}))
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_buy_now_creates_guest_order_without_cart_and_sends_email(self):
|
||
response = self.client.post(
|
||
reverse("orders:buy_now", kwargs={"item_id": self.item.id}),
|
||
data=self.checkout_data(),
|
||
)
|
||
|
||
order = Order.objects.get()
|
||
self.assertRedirects(response, reverse("orders:success", kwargs={"order_id": order.id}))
|
||
self.assertIsNone(order.user)
|
||
self.assertEqual(order.products_total, 1600)
|
||
self.assertEqual(order.delivery_total, 50)
|
||
self.assertEqual(order.floor_lift_total, 45)
|
||
self.assertEqual(order.total, 1695)
|
||
self.assertFalse(Cart.objects.exists())
|
||
|
||
order_item = OrderItem.objects.get(order=order)
|
||
self.assertEqual(order_item.item_name, "Кухонная мойка")
|
||
self.assertEqual(order_item.quantity, 1)
|
||
self.assertEqual(order_item.line_total, 1600)
|
||
|
||
self.assertIn(order.id, self.client.session["guest_order_ids"])
|
||
self.assertEqual(len(mail.outbox), 1)
|
||
self.assertEqual(mail.outbox[0].to, ["buyer@example.com"])
|
||
|
||
def test_guest_success_page_requires_session_order_access(self):
|
||
order = Order.objects.create(
|
||
user=None,
|
||
customer_name="Иван Петров",
|
||
phone="+375296881819",
|
||
email="buyer@example.com",
|
||
delivery_method=Order.DeliveryMethod.COURIER,
|
||
region="Минск",
|
||
address="ул. Тимирязева, 123",
|
||
payment_method=Order.PaymentMethod.CARD,
|
||
products_total=1600,
|
||
discount_total=0,
|
||
delivery_total=50,
|
||
floor_lift_total=45,
|
||
total=1695,
|
||
status=Order.Status.PAID,
|
||
)
|
||
|
||
response = self.client.get(reverse("orders:success", kwargs={"order_id": order.id}))
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_guest_success_page_allows_session_order_access(self):
|
||
response = self.client.post(
|
||
reverse("orders:buy_now", kwargs={"item_id": self.item.id}),
|
||
data=self.checkout_data(),
|
||
)
|
||
order = Order.objects.get()
|
||
self.assertRedirects(response, reverse("orders:success", kwargs={"order_id": order.id}))
|
||
|
||
success_response = self.client.get(reverse("orders:success", kwargs={"order_id": order.id}))
|
||
|
||
self.assertEqual(success_response.status_code, 200)
|
||
self.assertContains(success_response, f"Заказ №{order.id} оформлен")
|
||
|
||
def test_success_page_is_only_available_to_order_owner(self):
|
||
other_user = User.objects.create_user(
|
||
username="other-user",
|
||
email="other@example.com",
|
||
password="StrongPassword123",
|
||
is_active=True,
|
||
)
|
||
order = Order.objects.create(
|
||
user=self.user,
|
||
customer_name="Иван Петров",
|
||
phone="+375296881819",
|
||
email="buyer@example.com",
|
||
delivery_method=Order.DeliveryMethod.COURIER,
|
||
region="Минск",
|
||
address="ул. Тимирязева, 123",
|
||
payment_method=Order.PaymentMethod.CARD,
|
||
products_total=1600,
|
||
discount_total=0,
|
||
delivery_total=50,
|
||
floor_lift_total=45,
|
||
total=1695,
|
||
status=Order.Status.PAID,
|
||
)
|
||
self.client.force_login(other_user)
|
||
|
||
response = self.client.get(reverse("orders:success", kwargs={"order_id": order.id}))
|
||
|
||
self.assertEqual(response.status_code, 404)
|