All checks were successful
Build and Push Docker Image / build (push) Successful in 27s
489 lines
18 KiB
Python
489 lines
18 KiB
Python
from django.test import TestCase, override_settings
|
||
from django.contrib.auth import get_user_model
|
||
from django.core.exceptions import ValidationError
|
||
|
||
from favorites.models import Favorite
|
||
from items.models import Category, Item, ItemImage, Subcategory
|
||
from orders.models import Cart, CartItem
|
||
|
||
|
||
@override_settings(MEDIA_URL="/media/")
|
||
class CatalogViewTests(TestCase):
|
||
def setUp(self):
|
||
self.plumbing = Category.objects.create(name="Сантехника", slug="plumbing")
|
||
self.decor = Category.objects.create(name="Декор", slug="decor")
|
||
self.sinks = Subcategory.objects.create(name="Мойки", slug="sinks", category=self.plumbing)
|
||
self.taps = Subcategory.objects.create(name="Смесители", slug="taps", category=self.plumbing)
|
||
self.decor_lights = Subcategory.objects.create(name="Подсветка", slug="lights", category=self.decor)
|
||
self.sink = Item.objects.create(
|
||
name="Кухонная мойка",
|
||
slug="kitchen-sink",
|
||
category=self.plumbing,
|
||
subcategory=self.sinks,
|
||
description="Искусственный камень, врезная на столешницу",
|
||
price="1600.00",
|
||
is_available=True,
|
||
)
|
||
self.mirror = Item.objects.create(
|
||
name="Зеркало Terra",
|
||
slug="terra-mirror",
|
||
category=self.decor,
|
||
subcategory=self.decor_lights,
|
||
description="Холодная подсветка, фронтальная",
|
||
price="200.00",
|
||
is_available=False,
|
||
)
|
||
|
||
def test_catalog_page_shows_items_from_all_categories(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertContains(response, "Зеркало Terra")
|
||
|
||
def test_category_page_shows_only_selected_category_items(self):
|
||
response = self.client.get("/catalog/plumbing/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_category_page_shows_only_selected_category_subcategories(self):
|
||
response = self.client.get("/catalog/plumbing/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Мойки")
|
||
self.assertContains(response, "Смесители")
|
||
self.assertNotContains(response, 'href="/catalog/decor/?subcategory=lights"')
|
||
|
||
def test_subcategory_page_shows_only_selected_subcategory_items(self):
|
||
Item.objects.create(
|
||
name="Смеситель Mono",
|
||
slug="mono-tap",
|
||
category=self.plumbing,
|
||
subcategory=self.taps,
|
||
description="Однорычажный смеситель",
|
||
price="320.00",
|
||
is_available=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/plumbing/?subcategory=sinks")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Смеситель Mono")
|
||
|
||
def test_catalog_search_filters_items_by_name(self):
|
||
response = self.client.get("/catalog/", {"q": "мойка"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_catalog_search_is_case_insensitive(self):
|
||
response = self.client.get("/catalog/", {"q": "МОЙКА"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_category_search_filters_items_within_selected_category(self):
|
||
Item.objects.create(
|
||
name="Смеситель Terra",
|
||
slug="terra-tap",
|
||
category=self.plumbing,
|
||
subcategory=self.taps,
|
||
description="Смеситель для ванной",
|
||
price="450.00",
|
||
is_available=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/plumbing/", {"q": "terra"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Смеситель Terra")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_subcategory_search_filters_items_within_selected_subcategory(self):
|
||
Item.objects.create(
|
||
name="Terra Смеситель",
|
||
slug="terra-tap",
|
||
category=self.plumbing,
|
||
subcategory=self.taps,
|
||
description="Смеситель для раковины",
|
||
price="450.00",
|
||
is_available=True,
|
||
)
|
||
Item.objects.create(
|
||
name="Terra Мойка",
|
||
slug="terra-sink",
|
||
category=self.plumbing,
|
||
subcategory=self.sinks,
|
||
description="Кухонная мойка",
|
||
price="900.00",
|
||
is_available=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/plumbing/", {"subcategory": "sinks", "q": "terra"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Terra Мойка")
|
||
self.assertNotContains(response, "Terra Смеситель")
|
||
|
||
def test_min_price_filters_items(self):
|
||
response = self.client.get("/catalog/", {"min_price": "300"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_max_price_filters_items(self):
|
||
response = self.client.get("/catalog/", {"max_price": "300"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Зеркало Terra")
|
||
self.assertNotContains(response, "Кухонная мойка")
|
||
|
||
def test_price_range_filters_items(self):
|
||
Item.objects.create(
|
||
name="Смеситель Mono",
|
||
slug="mono-tap",
|
||
category=self.plumbing,
|
||
subcategory=self.taps,
|
||
description="Однорычажный смеситель",
|
||
price="320.00",
|
||
is_available=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/", {"min_price": "300", "max_price": "500"})
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Смеситель Mono")
|
||
self.assertNotContains(response, "Кухонная мойка")
|
||
self.assertNotContains(response, "Зеркало Terra")
|
||
|
||
def test_header_search_redirects_to_selected_category_url(self):
|
||
response = self.client.get(
|
||
"/catalog/",
|
||
{"category": "category:plumbing", "q": "terra", "min_price": "100", "max_price": "500"},
|
||
)
|
||
|
||
self.assertRedirects(response, "/catalog/plumbing/?q=terra&min_price=100&max_price=500")
|
||
|
||
def test_header_search_redirects_to_selected_subcategory_url(self):
|
||
response = self.client.get(
|
||
"/catalog/",
|
||
{"category": "subcategory:plumbing:sinks", "q": "terra", "min_price": "100", "max_price": "500"},
|
||
)
|
||
|
||
self.assertRedirects(
|
||
response,
|
||
"/catalog/plumbing/?subcategory=sinks&q=terra&min_price=100&max_price=500",
|
||
)
|
||
|
||
def test_unknown_subcategory_returns_404(self):
|
||
response = self.client.get("/catalog/plumbing/?subcategory=unknown")
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_subcategory_from_another_category_returns_404(self):
|
||
response = self.client.get("/catalog/plumbing/?subcategory=lights")
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_unknown_category_returns_404(self):
|
||
response = self.client.get("/catalog/unknown/")
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_sidebar_shows_all_categories_and_all_items_link(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, "Все товары")
|
||
self.assertContains(response, "Сантехника")
|
||
self.assertContains(response, "Декор")
|
||
|
||
def test_sidebar_links_preserve_active_search_and_price_filters(self):
|
||
response = self.client.get("/catalog/plumbing/", {"q": "terra", "min_price": "100", "max_price": "500"})
|
||
|
||
self.assertContains(response, 'href="/catalog/?q=terra&min_price=100&max_price=500"')
|
||
self.assertContains(response, 'href="/catalog/plumbing/?q=terra&min_price=100&max_price=500"')
|
||
self.assertContains(
|
||
response,
|
||
'href="/catalog/plumbing/?subcategory=sinks&q=terra&min_price=100&max_price=500"',
|
||
)
|
||
|
||
def test_catalog_forms_repeat_active_search_and_price_values(self):
|
||
response = self.client.get(
|
||
"/catalog/plumbing/",
|
||
{"subcategory": "sinks", "q": "terra", "min_price": "100", "max_price": "500"},
|
||
)
|
||
|
||
self.assertContains(response, 'name="q" value="terra"', html=False)
|
||
self.assertContains(response, 'name="min_price" value="100"', html=False)
|
||
self.assertContains(response, 'name="max_price" value="500"', html=False)
|
||
|
||
def test_catalog_price_filter_renders_range_slider(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, 'data-price-slider', html=False)
|
||
self.assertContains(response, 'type="range"', count=2, html=False)
|
||
self.assertContains(response, 'min="200"', html=False)
|
||
self.assertContains(response, 'max="1600"', html=False)
|
||
self.assertContains(response, 'src="/static/js/catalog-price-slider.js"', html=False)
|
||
|
||
def test_item_card_uses_primary_image(self):
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/secondary.jpg",
|
||
sort_order=0,
|
||
is_primary=False,
|
||
)
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/primary.jpg",
|
||
sort_order=10,
|
||
is_primary=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, "/media/items/primary.jpg")
|
||
self.assertNotContains(response, "/media/items/secondary.jpg")
|
||
|
||
def test_item_card_shows_subcategory(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, "Сантехника / Мойки")
|
||
|
||
def test_item_card_uses_first_ordered_image_without_primary(self):
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/second.jpg",
|
||
sort_order=20,
|
||
is_primary=False,
|
||
)
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/first.jpg",
|
||
sort_order=10,
|
||
is_primary=False,
|
||
)
|
||
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, "/media/items/first.jpg")
|
||
self.assertNotContains(response, "/media/items/second.jpg")
|
||
|
||
def test_catalog_item_card_links_to_detail_page(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, 'href="/catalog/plumbing/kitchen-sink/"')
|
||
self.assertContains(
|
||
response,
|
||
'<a class="item-card-overlay" href="/catalog/plumbing/kitchen-sink/"',
|
||
html=False,
|
||
)
|
||
|
||
def test_sidebar_product_cards_include_full_card_links(self):
|
||
sale_item = Item.objects.create(
|
||
name="Акционная ванна",
|
||
slug="sale-bath",
|
||
category=self.plumbing,
|
||
price="900.00",
|
||
discount_price="750.00",
|
||
is_available=True,
|
||
)
|
||
new_item = Item.objects.create(
|
||
name="Новый душ",
|
||
slug="new-shower",
|
||
category=self.plumbing,
|
||
price="500.00",
|
||
is_available=True,
|
||
is_new=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(
|
||
response,
|
||
f'<a class="sidebar-mini-card__overlay" href="/catalog/plumbing/{sale_item.slug}/"',
|
||
html=False,
|
||
)
|
||
self.assertContains(
|
||
response,
|
||
f'<a class="sidebar-mini-card__overlay" href="/catalog/plumbing/{new_item.slug}/"',
|
||
html=False,
|
||
)
|
||
|
||
def test_catalog_item_card_shows_favorite_button_for_authenticated_user(self):
|
||
user = get_user_model().objects.create_user(
|
||
username="ann",
|
||
email="ann@example.com",
|
||
password="StrongPassword123",
|
||
is_active=True,
|
||
)
|
||
self.client.force_login(user)
|
||
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, 'action="/favorites/toggle/"', html=False)
|
||
self.assertContains(response, "Добавить в избранное")
|
||
self.assertContains(response, 'name="item_id" value="', html=False)
|
||
|
||
def test_catalog_item_card_marks_favorited_item(self):
|
||
user = get_user_model().objects.create_user(
|
||
username="ann2",
|
||
email="ann2@example.com",
|
||
password="StrongPassword123",
|
||
is_active=True,
|
||
)
|
||
self.client.force_login(user)
|
||
Favorite.objects.create(user=user, item=self.sink)
|
||
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertContains(response, "Удалить из избранного")
|
||
self.assertContains(response, "is-active")
|
||
|
||
def test_detail_page_shows_item_data(self):
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/sink-primary.jpg",
|
||
alt="Главное фото мойки",
|
||
sort_order=0,
|
||
is_primary=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/plumbing/kitchen-sink/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, "Кухонная мойка")
|
||
self.assertContains(response, "Сантехника / Мойки")
|
||
self.assertContains(response, "Искусственный камень")
|
||
self.assertContains(response, "1600,00 руб.")
|
||
self.assertContains(response, "В наличии")
|
||
self.assertContains(response, "/media/items/sink-primary.jpg")
|
||
self.assertContains(response, "Главное фото мойки")
|
||
|
||
def test_detail_page_uses_primary_image_first(self):
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/secondary.jpg",
|
||
sort_order=0,
|
||
is_primary=False,
|
||
)
|
||
ItemImage.objects.create(
|
||
item=self.sink,
|
||
image="items/primary.jpg",
|
||
sort_order=10,
|
||
is_primary=True,
|
||
)
|
||
|
||
response = self.client.get("/catalog/plumbing/kitchen-sink/")
|
||
|
||
self.assertContains(response, '<img src="/media/items/primary.jpg"', html=False)
|
||
|
||
def test_detail_page_unknown_item_returns_404(self):
|
||
response = self.client.get("/catalog/plumbing/unknown/")
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_detail_page_shows_cart_link_for_item_already_in_cart(self):
|
||
user = get_user_model().objects.create_user(
|
||
username="cart-user",
|
||
email="cart-user@example.com",
|
||
password="StrongPassword123",
|
||
is_active=True,
|
||
)
|
||
cart = Cart.objects.create(user=user)
|
||
CartItem.objects.create(cart=cart, item=self.sink, quantity=1)
|
||
self.client.force_login(user)
|
||
|
||
response = self.client.get("/catalog/plumbing/kitchen-sink/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, 'href="/orders/cart/"', html=False)
|
||
self.assertContains(response, "Перейти в корзину")
|
||
|
||
def test_detail_page_guest_cart_cta_redirects_to_login(self):
|
||
response = self.client.get("/catalog/plumbing/kitchen-sink/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(
|
||
response,
|
||
'href="/users/login/?next=/catalog/plumbing/kitchen-sink/"',
|
||
html=False,
|
||
)
|
||
self.assertNotContains(response, "data-guest-cart-detail-button", html=False)
|
||
|
||
def test_detail_page_guest_buy_now_link_does_not_require_login(self):
|
||
response = self.client.get("/catalog/plumbing/kitchen-sink/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(response, f'href="/orders/buy/{self.sink.id}/"', html=False)
|
||
self.assertContains(response, "Купить")
|
||
|
||
def test_header_cart_link_redirects_guest_to_login(self):
|
||
response = self.client.get("/catalog/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertContains(
|
||
response,
|
||
'href="/users/login/?next=/orders/cart/"',
|
||
html=False,
|
||
)
|
||
|
||
def test_detail_page_wrong_category_returns_404(self):
|
||
response = self.client.get("/catalog/decor/kitchen-sink/")
|
||
|
||
self.assertEqual(response.status_code, 404)
|
||
|
||
def test_item_subcategory_must_belong_to_item_category(self):
|
||
item = Item(
|
||
name="Некорректный товар",
|
||
slug="invalid-item",
|
||
category=self.plumbing,
|
||
subcategory=self.decor_lights,
|
||
description="",
|
||
price="100.00",
|
||
is_available=True,
|
||
)
|
||
|
||
with self.assertRaises(ValidationError):
|
||
item.full_clean()
|
||
|
||
def test_admin_subcategories_endpoint_filters_by_category(self):
|
||
user = get_user_model().objects.create_superuser(
|
||
username="admin",
|
||
email="admin@example.com",
|
||
password="password",
|
||
)
|
||
self.client.force_login(user)
|
||
|
||
response = self.client.get(f"/admin/items/item/subcategories/?category_id={self.plumbing.id}")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertEqual(
|
||
response.json(),
|
||
{
|
||
"subcategories": [
|
||
{"id": self.sinks.id, "name": "Мойки"},
|
||
{"id": self.taps.id, "name": "Смесители"},
|
||
]
|
||
},
|
||
)
|
||
|
||
def test_admin_subcategories_endpoint_returns_empty_without_category(self):
|
||
user = get_user_model().objects.create_superuser(
|
||
username="admin",
|
||
email="admin@example.com",
|
||
password="password",
|
||
)
|
||
self.client.force_login(user)
|
||
|
||
response = self.client.get("/admin/items/item/subcategories/")
|
||
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertEqual(response.json(), {"subcategories": []})
|