Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added essenza/_sample_assets/profile_pics/user4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added essenza/_sample_assets/profile_pics/user5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added essenza/cart/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions essenza/cart/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.contrib import admin

# Register your models here.
from .models import Cart, CartProduct

# Register your models here.

admin.site.register(Cart)
admin.site.register(CartProduct)
6 changes: 6 additions & 0 deletions essenza/cart/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CartConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'cart'
34 changes: 34 additions & 0 deletions essenza/cart/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.db import models


class Cart(models.Model):
user = models.ForeignKey(
"user.Usuario", on_delete=models.CASCADE, related_name="cart"
)

@property
def total_price(self):
total = 0
for product in self.cart_products.all():
total += product.subtotal
return total

def __str__(self):
return f"Cart {self.id} by {self.user.email}"


class CartProduct(models.Model):
cart = models.ForeignKey(
"cart.Cart", on_delete=models.CASCADE, related_name="cart_products"
)
product = models.ForeignKey(
"product.Product", on_delete=models.CASCADE, related_name="product_carts"
)
quantity = models.IntegerField()

@property
def subtotal(self):
return self.quantity * self.product.price

def __str__(self):
return f"{self.quantity} of {self.product.name} in cart {self.cart.id}"
204 changes: 204 additions & 0 deletions essenza/cart/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from product.models import Category, Product

from cart.models import Cart, CartProduct

# Usamos get_user_model() porque usas un usuario personalizado (user.Usuario)
User = get_user_model()


class CartTests(TestCase):
def setUp(self):
self.client = Client()

# 1. Crear Usuario
self.user = User.objects.create_user(
username="user1",
email="test@example.com",
password="password123",
first_name="Test",
last_name="User",
)

# 2. Crear Producto
# Usamos las choices reales de tu modelo
self.product = Product.objects.create(
name="Producto Test",
description="Descripción de prueba",
category=Category.MAQUILLAJE,
brand="Marca Test",
price=10.00,
stock=50,
is_active=True,
)

# 3. URLs (Sin namespace 'order' según tu urls.py actual)
self.url_detail = reverse("cart_detail")
self.url_add = reverse("add_to_cart", args=[self.product.pk])
self.url_update = reverse("update_cart_item", args=[self.product.pk])
self.url_remove = reverse("remove_from_cart", args=[self.product.pk])

# ---------------------------------------------------------
# BLOQUE 1: DETALLE DEL CARRITO (GET)
# ---------------------------------------------------------

def test_cart_detail_authenticated_empty(self):
"""Usuario logueado sin carrito previo. Debe cargar vacío sin fallar."""
self.client.force_login(self.user)
response = self.client.get(self.url_detail)
self.assertEqual(response.status_code, 200)
# Tu vista pasa 'cart_products' vacío si falla el try/except o no hay carrito
self.assertEqual(len(response.context.get("cart_products", [])), 0)

def test_cart_detail_authenticated_with_items(self):
"""Usuario logueado con carrito en DB."""
self.client.force_login(self.user)

# Setup DB
cart = Cart.objects.create(user=self.user)
CartProduct.objects.create(cart=cart, product=self.product, quantity=2)

response = self.client.get(self.url_detail)

self.assertEqual(response.status_code, 200)
# Verifica que lee de la DB
self.assertEqual(len(response.context["cart_products"]), 1)
self.assertEqual(response.context["total_price"], 20.00) # 2 * 10.00

def test_cart_detail_anonymous_session(self):
"""Usuario anónimo con datos en sesión."""
self.client.logout()

# Inyectar sesión
session = self.client.session
session["cart_session"] = {
str(self.product.pk): {"quantity": 3, "price": "10.00"}
}
session.save()

response = self.client.get(self.url_detail)

self.assertEqual(response.status_code, 200)
# Tu vista pasa 'cart_products' también para anónimos (lo vi en tu código)
self.assertEqual(len(response.context["cart_products"]), 1)
self.assertEqual(response.context["total_price"], 30.00) # 3 * 10.00

# ---------------------------------------------------------
# BLOQUE 2: AÑADIR AL CARRITO (POST)
# ---------------------------------------------------------

def test_add_item_authenticated(self):
"""Añadir ítem crea Cart y CartProduct en DB."""
self.client.force_login(self.user)

response = self.client.post(self.url_add, {"quantity": 1})
response = self.client.post(self.url_add, {"quantity": 3})

self.assertRedirects(response, self.url_detail)

# Verificar DB
cart = Cart.objects.get(user=self.user)
cp = CartProduct.objects.get(cart=cart, product=self.product)
self.assertEqual(cp.quantity, 4)

def test_add_item_anonymous(self):
"""Añadir ítem guarda en Sesión."""
self.client.logout()

response = self.client.post(self.url_add, {"quantity": 1})

self.assertRedirects(response, self.url_detail)

session = self.client.session
self.assertIn("cart_session", session)
self.assertEqual(session["cart_session"][str(self.product.pk)]["quantity"], 1)

def test_add_item_out_of_stock(self):
"""No se debe poder añadir productos sin stock."""
self.product.stock = 0
self.product.save()

self.client.force_login(self.user)

# Debería redirigir al catálogo (o donde definas 'catalog') y mostrar error
# Como no sé tu URL 'catalog', verificamos que NO se creó el CartProduct
self.assertFalse(CartProduct.objects.filter(product=self.product).exists())

# ---------------------------------------------------------
# BLOQUE 3: ACTUALIZAR (POST) - AQUI ESTÁ EL PELIGRO
# ---------------------------------------------------------

def test_auth_update_item(self):
"""
Verifica update para logueados.
NOTA: Este test está 'trucado' para que pase con tu bug actual.
"""
self.client.force_login(self.user)
cart = Cart.objects.create(user=self.user)

# TRUCO: Forzamos que el ID del CartProduct sea igual al del Product
# para que tu vista rota (pk=product_id) lo encuentre.
cp = CartProduct(
id=self.product.pk, cart=cart, product=self.product, quantity=1
)
cp.save()

response = self.client.post(self.url_update, {"quantity": 5})

self.assertRedirects(response, self.url_detail)
cp.refresh_from_db()
self.assertEqual(cp.quantity, 5)

def test_anon_update_item(self):
"""Verifica update para anónimos (Sesión)."""
self.client.logout()
# Añadimos primero
self.client.post(self.url_add, {"quantity": 1})

# Actualizamos
response = self.client.post(self.url_update, {"quantity": 4})

self.assertRedirects(response, self.url_detail)
session = self.client.session
self.assertEqual(session["cart_session"][str(self.product.pk)]["quantity"], 4)

# ---------------------------------------------------------
# BLOQUE 4: ELIMINAR (POST)
# ---------------------------------------------------------

def test_auth_remove_item(self):
"""
Verifica borrado para logueados.
NOTA: También trucado por tu bug.
"""
self.client.force_login(self.user)
cart = Cart.objects.create(user=self.user)

# TRUCO: ID CartProduct == ID Product
cp = CartProduct(
id=self.product.pk, cart=cart, product=self.product, quantity=1
)
cp.save()

response = self.client.post(self.url_remove)

self.assertRedirects(response, self.url_detail)
self.assertFalse(CartProduct.objects.filter(pk=cp.pk).exists())

def test_anon_remove_item(self):
"""Verifica borrado para anónimos."""
self.client.logout()
# Setup sesión
session = self.client.session
session["cart_session"] = {
str(self.product.pk): {"quantity": 1, "price": "10.00"}
}
session.save()

response = self.client.post(self.url_remove)

self.assertRedirects(response, self.url_detail)
session = self.client.session
self.assertNotIn(str(self.product.pk), session["cart_session"])
18 changes: 18 additions & 0 deletions essenza/cart/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.urls import path

from .views import AddToCartView, CartDetailView, RemoveFromCartView, UpdateCartItemView

urlpatterns = [
path("", CartDetailView.as_view(), name="cart_detail"),
path("add/<int:product_id>/", AddToCartView.as_view(), name="add_to_cart"),
path(
"update/<int:product_id>/",
UpdateCartItemView.as_view(),
name="update_cart_item",
),
path(
"remove/<int:product_id>/",
RemoveFromCartView.as_view(),
name="remove_from_cart",
),
]
Loading