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")