diff --git a/essenza/product/tests.py b/essenza/product/tests.py index a68511e..97c12b6 100644 --- a/essenza/product/tests.py +++ b/essenza/product/tests.py @@ -1,6 +1,7 @@ from decimal import Decimal from django.contrib.auth import get_user_model +from django.contrib.messages import get_messages # Para probar mensajes from django.test import TestCase from django.urls import reverse from django.utils import timezone @@ -439,3 +440,148 @@ def test_catalog_detail_returns_404_for_nonexistent_product(self): url = reverse("catalog_detail", args=[9999]) response = self.client.get(url) self.assertEqual(response.status_code, 404) + + +class StockTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user( + username="user", + email="user@example.com", + password="pass1234", + role="user", + ) + cls.admin = User.objects.create_user( + username="admin", + email="admin@example.com", + password="pass1234", + role="admin", + ) + + cls.product_high = Product.objects.create( + name="Producto Alto", stock=20, price=10 + ) + cls.product_low = Product.objects.create( + name="Producto Bajo", stock=5, price=10 + ) + cls.product_out = Product.objects.create( + name="Producto Agotado", stock=0, price=10 + ) + + cls.stock_url = reverse("stock") + cls.login_url = reverse("login") + cls.dashboard_url = reverse("dashboard") + + # --- TESTS DE ACCESO --- + + def test_anonymous_user_redirects_to_dashboard(self): + resp = self.client.get(self.stock_url) + self.assertEqual(resp.status_code, 302) + self.assertRedirects(resp, self.dashboard_url) + + def test_non_admin_user_redirects_to_dashboard(self): + self.client.login(email=self.user.email, password="pass1234") + resp = self.client.get(self.stock_url) + self.assertEqual(resp.status_code, 302) + self.assertRedirects(resp, self.dashboard_url) + + def test_admin_user_succeeds_get(self): + self.client.login(email=self.admin.email, password="pass1234") + resp = self.client.get(self.stock_url) + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "product/stock.html") + + # --- TESTS DE FUNCIONALIDAD --- + + def test_stock_page_shows_all_products(self): + self.client.login(email=self.admin.email, password="pass1234") + resp = self.client.get(self.stock_url) + + # Comprobamos que aparecen los 3 productos + self.assertEqual(len(resp.context["products"]), 3) + + # Comprobamos el HTML + self.assertContains(resp, "Producto Alto") + self.assertContains( + resp, 'En Stock: 20', html=True + ) + + self.assertContains(resp, "Producto Bajo") + self.assertContains( + resp, 'Stock Bajo: 5', html=True + ) + + self.assertContains(resp, "Producto Agotado") + self.assertContains( + resp, 'Agotado (0)', html=True + ) + + def test_post_admin_updates_stock_successfully(self): + """5. CORRECTO: Un admin puede actualizar el stock (Test 7 en tu código).""" + self.client.login(email=self.admin.email, password="pass1234") + + self.assertEqual(self.product_high.stock, 20) # Stock inicial + + data = {"product_id": self.product_high.pk, "stock": 15} + resp = self.client.post( + self.stock_url, data, follow=True + ) # Se hace una petición POST para actualizar el stock a 15 + + # Comprobamos que volvemos a la página de stock + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "product/stock.html") + + # Comprobamos que la base de datos se actualizó correctamente + self.product_high.refresh_from_db() + self.assertEqual(self.product_high.stock, 15) + + # Comprobamos el mensaje de éxito + messages = list(get_messages(resp.context["request"])) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "Stock de 'Producto Alto' actualizado a 15.") + + def test_post_admin_invalid_product_returns_404(self): + self.client.login(email=self.admin.email, password="pass1234") + data = {"product_id": 999, "stock": 15} # ID 999 no existe + + resp = self.client.post(self.stock_url, data) + self.assertEqual(resp.status_code, 404) + + def test_post_admin_invalid_stock_value_shows_error(self): + self.client.login(email=self.admin.email, password="pass1234") + + # Enviamos un valor de stock no numérico + data = {"product_id": self.product_high.pk, "stock": "abc"} + resp = self.client.post(self.stock_url, data, follow=True) + + # Comprobamos que volvemos a la página de stock + self.assertEqual(resp.status_code, 200) + + # Comprobamos que el stock NO se actualizó + self.product_high.refresh_from_db() + self.assertEqual(self.product_high.stock, 20) + + # Comprobamos el mensaje de error + messages = list(get_messages(resp.context["request"])) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), "El valor de stock 'abc' no es un número válido." + ) + + def test_post_admin_negative_stock_value_shows_error(self): + self.client.login(email=self.admin.email, password="pass1234") + + # Enviamos un valor de stock negativo y comprobamos el error + data = {"product_id": self.product_high.pk, "stock": "-5"} + resp = self.client.post(self.stock_url, data, follow=True) + + self.assertEqual(resp.status_code, 200) + + self.product_high.refresh_from_db() + self.assertEqual(self.product_high.stock, 20) # No cambia + + messages = list(get_messages(resp.context["request"])) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), "El valor de stock '-5' no es un número válido." + ) diff --git a/essenza/product/views.py b/essenza/product/views.py index 4869951..4948d7b 100644 --- a/essenza/product/views.py +++ b/essenza/product/views.py @@ -53,7 +53,10 @@ class StockView(LoginRequiredMixin, UserPassesTestMixin, View): def test_func(self): return self.request.user.is_authenticated and self.request.user.role == "admin" - # Solo se ejecutan métodos GET y POST si el usuario pasa la prueba + # Redirige a 'dashboard' si no pasa el test_func + def handle_no_permission(self): + return redirect("dashboard") + def get(self, request): # Carga y muestra todos los productos ordenados por nombre products = Product.objects.all().order_by("name") @@ -62,21 +65,27 @@ def get(self, request): def post(self, request): # Coge datos del formulario para actualizar stock product_id = request.POST.get("product_id") - stock = request.POST.get("stock") - - # Encuentra el producto por su ID - product = Product.objects.get(pk=product_id) - if not product: - messages.error(request, "Producto no encontrado.") - return redirect("stock") - - # Actualiza el stock del producto - new_stock = int(stock or 0) - product.stock = new_stock - product.save(update_fields=["stock"]) - messages.success( - request, f"Stock de '{product.name}' actualizado a {new_stock}." - ) + stock_value = request.POST.get("stock") # Renombrado para claridad + + product = get_object_or_404(Product, pk=product_id) + + try: + # Comprobamos si el valor es un número + new_stock = int(stock_value or 0) + if new_stock < 0: + # No permitir stock negativo + raise ValueError("El stock no puede ser negativo") + + product.stock = new_stock + product.save(update_fields=["stock"]) + messages.success( + request, f"Stock de '{product.name}' actualizado a {new_stock}." + ) + + except (ValueError, TypeError): + messages.error( + request, f"El valor de stock '{stock_value}' no es un número válido." + ) # Recarga la misma página return redirect("stock")