3e76782a0c
- Enhanced User model tests with creation, registration status, password hashing, and unique username checks. - Added extensive tests for VerificationCode model including creation, uniqueness, and relation to User. - Implemented Category model tests covering creation, uniqueness, and string representation. - Expanded Product model tests to include creation, default values, VAT calculations, and deletion behavior. - Introduced StockReservation and StockReservationItem model tests for creation, status checks, and unique constraints. - Developed Cart and CartItem model tests focusing on creation, total calculations, and item uniqueness. - Added Order and OrderItem model tests for creation, status management, and string representation. - Included SavedPaymentMethod and ShippingAddress model tests for creation, default settings, and ordering.
1254 lines
45 KiB
Python
1254 lines
45 KiB
Python
from django.test import TestCase
|
|
from django.contrib.auth import get_user_model
|
|
from django.utils import timezone
|
|
from django.db import IntegrityError
|
|
from datetime import timedelta
|
|
from .models import (
|
|
User, VerificationCode, Category, Image, Product,
|
|
StockReservation, StockReservationItem, Cart, CartItem,
|
|
Order, OrderItem, OrderMessage, SavedPaymentMethod, ShippingAddress
|
|
)
|
|
from .vars import VAT_RATE, TRANSACTION_CODE_PREFIX
|
|
import string
|
|
import random
|
|
|
|
|
|
# ==================== USER MODEL TESTS ====================
|
|
class UserModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo User."""
|
|
|
|
def setUp(self):
|
|
self.user_data = {
|
|
"username": "testuser",
|
|
"email": "test@example.com",
|
|
"first_name": "Test",
|
|
"last_name": "User",
|
|
"password": "TestPassword123!"
|
|
}
|
|
|
|
def test_user_creation_with_defaults(self):
|
|
"""Usuario nuevo debe tener estado CONFIRMATION_REQUIRED por defecto."""
|
|
user = User.objects.create(**self.user_data)
|
|
self.assertEqual(user.registration_status, User.RegisterStatus.CONFIRMATION_REQUIRED)
|
|
self.assertEqual(user.username, "testuser")
|
|
self.assertEqual(user.email, "test@example.com")
|
|
|
|
def test_user_registration_status_choices(self):
|
|
"""Todos los estados de registro deben ser válidos."""
|
|
for status_code, status_label in User.RegisterStatus.choices:
|
|
user = User.objects.create(
|
|
username=f"user_{status_code}",
|
|
registration_status=status_code
|
|
)
|
|
self.assertEqual(user.registration_status, status_code)
|
|
|
|
def test_user_password_hashing(self):
|
|
"""Las contraseñas deben hashearse correctamente."""
|
|
user = User.objects.create(username="testuser")
|
|
password = "SecurePassword123!"
|
|
user.set_password(password)
|
|
user.save()
|
|
|
|
self.assertTrue(user.check_password(password))
|
|
self.assertFalse(user.check_password("WrongPassword"))
|
|
|
|
def test_user_can_set_active_status(self):
|
|
"""Usuario puede cambiar a estado ACTIVE."""
|
|
user = User.objects.create(username="testuser")
|
|
user.registration_status = User.RegisterStatus.ACTIVE
|
|
user.save()
|
|
|
|
refreshed = User.objects.get(username="testuser")
|
|
self.assertEqual(refreshed.registration_status, User.RegisterStatus.ACTIVE)
|
|
|
|
def test_user_can_be_banned(self):
|
|
"""Usuario puede ser marcado como BANNED."""
|
|
user = User.objects.create(username="testuser")
|
|
user.registration_status = User.RegisterStatus.BANNED
|
|
user.save()
|
|
|
|
refreshed = User.objects.get(username="testuser")
|
|
self.assertEqual(refreshed.registration_status, User.RegisterStatus.BANNED)
|
|
|
|
def test_multiple_users_unique_username(self):
|
|
"""Dos usuarios no pueden tener el mismo username."""
|
|
User.objects.create(username="unique_user")
|
|
with self.assertRaises(IntegrityError):
|
|
User.objects.create(username="unique_user")
|
|
|
|
def test_user_str_representation(self):
|
|
"""La representación string del usuario debe ser correcta."""
|
|
user = User.objects.create(username="testuser", first_name="Test")
|
|
# AbstractUser generalmente devuelve username
|
|
self.assertIn("testuser", str(user))
|
|
|
|
def test_user_email_validation(self):
|
|
"""Email debe ser válido (Django validation)."""
|
|
user = User.objects.create(username="test", email="valid@example.com")
|
|
self.assertEqual(user.email, "valid@example.com")
|
|
|
|
def test_user_with_empty_optional_fields(self):
|
|
"""Usuario puede ser creado sin first_name/last_name."""
|
|
user = User.objects.create(username="minimal_user")
|
|
self.assertEqual(user.first_name, "")
|
|
self.assertEqual(user.last_name, "")
|
|
|
|
def test_user_related_products(self):
|
|
"""User debe estar relacionado con sus productos creados."""
|
|
user = User.objects.create(username="creator")
|
|
category = Category.objects.create(name="TestCat")
|
|
product = Product.objects.create(
|
|
name="TestProd", category=category, creator=user
|
|
)
|
|
|
|
self.assertIn(product, user.created_products.all())
|
|
|
|
def test_user_related_orders(self):
|
|
"""User debe estar relacionado con sus pedidos."""
|
|
user = User.objects.create(username="buyer")
|
|
order = Order.objects.create(buyer=user, status=Order.STATUS_PAID)
|
|
|
|
self.assertIn(order, user.orders.all())
|
|
|
|
|
|
# ==================== VERIFICATION CODE MODEL TESTS ====================
|
|
class VerificationCodeModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo VerificationCode."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="testuser")
|
|
|
|
def test_verification_code_creation(self):
|
|
"""Código de verificación debe crearse correctamente."""
|
|
code = VerificationCode.generate(
|
|
self.user,
|
|
VerificationCode.VerificationModes.VERIFY_ACCOUNT
|
|
)
|
|
|
|
self.assertIsNotNone(code)
|
|
self.assertEqual(code.user, self.user)
|
|
self.assertEqual(code.code_mode, VerificationCode.VerificationModes.VERIFY_ACCOUNT)
|
|
|
|
def test_verification_code_uniqueness(self):
|
|
"""Dos códigos no pueden tener el mismo código."""
|
|
code1 = VerificationCode.generate(self.user, VerificationCode.VerificationModes.VERIFY_ACCOUNT)
|
|
code2 = VerificationCode.generate(self.user, VerificationCode.VerificationModes.VERIFY_ACCOUNT)
|
|
|
|
self.assertNotEqual(code1.code, code2.code)
|
|
|
|
def test_verification_code_for_password_reset(self):
|
|
"""Código puede ser para reset de contraseña."""
|
|
code = VerificationCode.generate(
|
|
self.user,
|
|
VerificationCode.VerificationModes.RESET_PASSWORD
|
|
)
|
|
|
|
self.assertEqual(code.code_mode, VerificationCode.VerificationModes.RESET_PASSWORD)
|
|
|
|
def test_verification_code_fifty_creations(self):
|
|
"""50 códigos pueden crearse sin conflictos."""
|
|
codes = []
|
|
for i in range(50):
|
|
mode = random.choice([
|
|
VerificationCode.VerificationModes.VERIFY_ACCOUNT,
|
|
VerificationCode.VerificationModes.RESET_PASSWORD
|
|
])
|
|
code = VerificationCode.generate(self.user, mode)
|
|
codes.append(code.code)
|
|
|
|
# Verificar que todos son únicos
|
|
self.assertEqual(len(codes), len(set(codes)))
|
|
|
|
def test_verification_code_related_to_user(self):
|
|
"""Código debe estar relacionado correctamente con usuario."""
|
|
code = VerificationCode.generate(self.user, VerificationCode.VerificationModes.VERIFY_ACCOUNT)
|
|
|
|
self.assertIn(code, self.user.user_belongsto.all())
|
|
|
|
def test_verification_code_str_representation(self):
|
|
"""La representación string del código debe ser válida."""
|
|
code = VerificationCode.generate(self.user, VerificationCode.VerificationModes.VERIFY_ACCOUNT)
|
|
code_str = str(code)
|
|
self.assertIsNotNone(code_str)
|
|
|
|
|
|
# ==================== CATEGORY MODEL TESTS ====================
|
|
class CategoryModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo Category."""
|
|
|
|
def test_category_creation_basic(self):
|
|
"""Categoría debe crearse correctamente."""
|
|
category = Category.objects.create(name="Electronics")
|
|
self.assertEqual(category.name, "Electronics")
|
|
|
|
def test_category_name_unique(self):
|
|
"""Dos categorías no pueden tener el mismo nombre."""
|
|
Category.objects.create(name="UniqueCategory")
|
|
with self.assertRaises(IntegrityError):
|
|
Category.objects.create(name="UniqueCategory")
|
|
|
|
def test_category_hundred_creations(self):
|
|
"""100 categorías pueden crearse sin problemas."""
|
|
categories = []
|
|
for i in range(100):
|
|
cat = Category.objects.create(name=f"Category_{i}_{random.randint(1000, 9999)}")
|
|
categories.append(cat)
|
|
|
|
self.assertEqual(len(categories), 100)
|
|
self.assertEqual(Category.objects.count(), 100)
|
|
|
|
def test_category_str_representation(self):
|
|
"""La representación string debe ser el nombre."""
|
|
category = Category.objects.create(name="TestCategory")
|
|
self.assertEqual(str(category), "TestCategory")
|
|
|
|
def test_category_empty_name_not_allowed(self):
|
|
"""Categoría puede crearse con nombre vacío a nivel de BD (validar en forms)."""
|
|
# Django permite guardar campos vacíos sin NULL constraint
|
|
# La validación debe hacerse en forms o modelo validators
|
|
cat = Category.objects.create(name="")
|
|
self.assertEqual(cat.name, "")
|
|
self.assertTrue(Category.objects.filter(name="").exists())
|
|
|
|
def test_category_special_characters_in_name(self):
|
|
"""Categoría puede tener caracteres especiales."""
|
|
category = Category.objects.create(name="Electrónica & Gadgets™")
|
|
self.assertEqual(category.name, "Electrónica & Gadgets™")
|
|
|
|
def test_category_deletion(self):
|
|
"""Categoría puede ser eliminada."""
|
|
category = Category.objects.create(name="ToDelete")
|
|
cat_id = category.id
|
|
category.delete()
|
|
|
|
self.assertFalse(Category.objects.filter(id=cat_id).exists())
|
|
|
|
|
|
# ==================== IMAGE MODEL TESTS ====================
|
|
class ImageModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo Image."""
|
|
|
|
def test_image_creation_minimal(self):
|
|
"""Imagen debe crearse con mínimos campos requeridos."""
|
|
image = Image.objects.create(
|
|
name="TestImage",
|
|
image="path/to/image.jpg",
|
|
alt="Test Alt Text"
|
|
)
|
|
self.assertEqual(image.name, "TestImage")
|
|
self.assertEqual(image.alt, "Test Alt Text")
|
|
|
|
def test_image_alt_text_optional(self):
|
|
"""Alt text puede estar vacío."""
|
|
image = Image.objects.create(
|
|
name="TestImage",
|
|
image="path/to/image.jpg",
|
|
alt=""
|
|
)
|
|
self.assertEqual(image.alt, "")
|
|
|
|
def test_image_str_representation(self):
|
|
"""La representación string debe ser el nombre."""
|
|
image = Image.objects.create(
|
|
name="MyImage",
|
|
image="path/to/image.jpg"
|
|
)
|
|
self.assertEqual(str(image), "MyImage")
|
|
|
|
def test_image_name_default_empty(self):
|
|
"""Nombre tiene default vacío."""
|
|
image = Image(image="path/to/image.jpg")
|
|
self.assertEqual(image.name, "")
|
|
|
|
def test_image_alt_default_empty(self):
|
|
"""Alt text tiene default vacío."""
|
|
image = Image(name="Test", image="path/to/image.jpg")
|
|
self.assertEqual(image.alt, "")
|
|
|
|
|
|
# ==================== PRODUCT MODEL TESTS ====================
|
|
class ProductModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo Product."""
|
|
|
|
def setUp(self):
|
|
self.category = Category.objects.create(name="TestCategory")
|
|
self.user = User.objects.create(username="seller")
|
|
self.image = Image.objects.create(
|
|
name="MainImage",
|
|
image="path/to/main.jpg"
|
|
)
|
|
|
|
def test_product_creation_full(self):
|
|
"""Producto debe crearse con todos los campos."""
|
|
product = Product.objects.create(
|
|
name="TestProduct",
|
|
description="Full description",
|
|
briefdesc="Brief",
|
|
price=99.99,
|
|
stock=50,
|
|
category=self.category,
|
|
primary_image=self.image,
|
|
creator=self.user
|
|
)
|
|
|
|
self.assertEqual(product.name, "TestProduct")
|
|
self.assertEqual(product.price, 99.99)
|
|
self.assertEqual(product.stock, 50)
|
|
|
|
def test_product_defaults(self):
|
|
"""Producto debe tener valores por defecto correctos."""
|
|
product = Product.objects.create(
|
|
name="MinimalProduct",
|
|
category=self.category
|
|
)
|
|
|
|
self.assertEqual(product.description, "")
|
|
self.assertEqual(product.briefdesc, "")
|
|
self.assertEqual(product.price, 0)
|
|
self.assertEqual(product.stock, 0)
|
|
self.assertIsNone(product.primary_image)
|
|
|
|
def test_product_get_price_with_vat(self):
|
|
"""Precio con IVA debe calcularse correctamente."""
|
|
product = Product.objects.create(
|
|
name="VATProduct",
|
|
price=100,
|
|
category=self.category
|
|
)
|
|
|
|
expected = round(100 * (1 + VAT_RATE), 2)
|
|
self.assertEqual(product.get_price_with_vat(), expected)
|
|
self.assertEqual(product.get_price_with_vat(), 121.0)
|
|
|
|
def test_product_get_vat_amount(self):
|
|
"""Cantidad de IVA debe calcularse correctamente."""
|
|
product = Product.objects.create(
|
|
name="VATProduct",
|
|
price=100,
|
|
category=self.category
|
|
)
|
|
|
|
expected = round(100 * VAT_RATE, 2)
|
|
self.assertEqual(product.get_vat_amount(), expected)
|
|
self.assertEqual(product.get_vat_amount(), 21.0)
|
|
|
|
def test_product_with_negative_price_allowed(self):
|
|
"""Campo price es FloatField, permite valores negativos (validar en forms)."""
|
|
product = Product.objects.create(
|
|
name="NegativePrice",
|
|
price=-10,
|
|
category=self.category
|
|
)
|
|
self.assertEqual(product.price, -10)
|
|
|
|
def test_product_with_zero_stock(self):
|
|
"""Producto puede tener stock 0."""
|
|
product = Product.objects.create(
|
|
name="NoStock",
|
|
stock=0,
|
|
category=self.category
|
|
)
|
|
self.assertEqual(product.stock, 0)
|
|
|
|
def test_product_str_representation(self):
|
|
"""La representación string debe incluir nombre y precio."""
|
|
product = Product.objects.create(
|
|
name="StrProduct",
|
|
price=49.99,
|
|
category=self.category
|
|
)
|
|
expected = f"StrProduct 49.99"
|
|
self.assertEqual(str(product), expected)
|
|
|
|
def test_product_secondary_images_many_to_many(self):
|
|
"""Producto puede tener múltiples imágenes secundarias."""
|
|
product = Product.objects.create(
|
|
name="MultiImageProduct",
|
|
category=self.category
|
|
)
|
|
|
|
image1 = Image.objects.create(name="Image1", image="path1.jpg")
|
|
image2 = Image.objects.create(name="Image2", image="path2.jpg")
|
|
|
|
product.secondary_images.add(image1, image2)
|
|
|
|
self.assertEqual(product.secondary_images.count(), 2)
|
|
self.assertIn(image1, product.secondary_images.all())
|
|
self.assertIn(image2, product.secondary_images.all())
|
|
|
|
def test_product_creator_optional(self):
|
|
"""Producto puede crearse sin creator."""
|
|
product = Product.objects.create(
|
|
name="NoCreator",
|
|
category=self.category,
|
|
creator=None
|
|
)
|
|
self.assertIsNone(product.creator)
|
|
|
|
def test_product_deletion_cascades(self):
|
|
"""Eliminar producto debe mantener categoría."""
|
|
product = Product.objects.create(
|
|
name="ToDelete",
|
|
category=self.category
|
|
)
|
|
product_id = product.id
|
|
product.delete()
|
|
|
|
self.assertFalse(Product.objects.filter(id=product_id).exists())
|
|
self.assertTrue(Category.objects.filter(id=self.category.id).exists())
|
|
|
|
def test_product_hundred_creations(self):
|
|
"""100 productos pueden crearse correctamente."""
|
|
products = []
|
|
for i in range(100):
|
|
product = Product.objects.create(
|
|
name=f"Product_{i}",
|
|
price=float(i),
|
|
stock=i,
|
|
category=self.category,
|
|
creator=self.user
|
|
)
|
|
products.append(product)
|
|
|
|
self.assertEqual(len(products), 100)
|
|
self.assertEqual(Product.objects.count(), 100)
|
|
|
|
|
|
# ==================== STOCK RESERVATION MODEL TESTS ====================
|
|
class StockReservationModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo StockReservation."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="testuser")
|
|
self.expires_at = timezone.now() + timedelta(minutes=5)
|
|
|
|
def test_stock_reservation_creation_user(self):
|
|
"""Reserva de stock para usuario autenticado."""
|
|
reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=StockReservation.PAYMENT_STRIPE,
|
|
expires_at=self.expires_at
|
|
)
|
|
|
|
self.assertEqual(reservation.user, self.user)
|
|
self.assertEqual(reservation.status, StockReservation.STATUS_ACTIVE)
|
|
|
|
def test_stock_reservation_creation_session(self):
|
|
"""Reserva de stock para sesión anónima."""
|
|
reservation = StockReservation.objects.create(
|
|
session_key="abc123def456",
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=StockReservation.PAYMENT_PAYPAL,
|
|
expires_at=self.expires_at
|
|
)
|
|
|
|
self.assertEqual(reservation.session_key, "abc123def456")
|
|
self.assertIsNone(reservation.user)
|
|
|
|
def test_stock_reservation_status_choices(self):
|
|
"""Todos los estados deben ser válidos."""
|
|
statuses = [
|
|
StockReservation.STATUS_ACTIVE,
|
|
StockReservation.STATUS_COMPLETED,
|
|
StockReservation.STATUS_CANCELLED,
|
|
StockReservation.STATUS_EXPIRED
|
|
]
|
|
|
|
for i, status in enumerate(statuses):
|
|
reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=status,
|
|
payment_method=StockReservation.PAYMENT_STRIPE,
|
|
expires_at=self.expires_at
|
|
)
|
|
self.assertEqual(reservation.status, status)
|
|
|
|
def test_stock_reservation_payment_methods(self):
|
|
"""Ambos métodos de pago deben ser válidos."""
|
|
for method in [StockReservation.PAYMENT_STRIPE, StockReservation.PAYMENT_PAYPAL]:
|
|
reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=method,
|
|
expires_at=self.expires_at
|
|
)
|
|
self.assertEqual(reservation.payment_method, method)
|
|
|
|
def test_stock_reservation_timestamps(self):
|
|
"""Las timestamps deben establecerse automáticamente."""
|
|
reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=StockReservation.PAYMENT_STRIPE,
|
|
expires_at=self.expires_at
|
|
)
|
|
|
|
self.assertIsNotNone(reservation.created_at)
|
|
self.assertIsNotNone(reservation.updated_at)
|
|
self.assertLessEqual(reservation.created_at, timezone.now())
|
|
|
|
def test_stock_reservation_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=StockReservation.PAYMENT_STRIPE,
|
|
expires_at=self.expires_at
|
|
)
|
|
|
|
reservation_str = str(reservation)
|
|
self.assertIn("Reserva", reservation_str)
|
|
self.assertIn("active", reservation_str)
|
|
|
|
|
|
# ==================== STOCK RESERVATION ITEM MODEL TESTS ====================
|
|
class StockReservationItemModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo StockReservationItem."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="testuser")
|
|
self.category = Category.objects.create(name="TestCat")
|
|
self.product = Product.objects.create(
|
|
name="TestProd",
|
|
category=self.category
|
|
)
|
|
self.reservation = StockReservation.objects.create(
|
|
user=self.user,
|
|
status=StockReservation.STATUS_ACTIVE,
|
|
payment_method=StockReservation.PAYMENT_STRIPE,
|
|
expires_at=timezone.now() + timedelta(minutes=5)
|
|
)
|
|
|
|
def test_reservation_item_creation(self):
|
|
"""Item de reserva debe crearse correctamente."""
|
|
item = StockReservationItem.objects.create(
|
|
reservation=self.reservation,
|
|
product=self.product,
|
|
quantity=10
|
|
)
|
|
|
|
self.assertEqual(item.quantity, 10)
|
|
self.assertEqual(item.product, self.product)
|
|
|
|
def test_reservation_item_default_quantity(self):
|
|
"""Cantidad por defecto es 1."""
|
|
item = StockReservationItem.objects.create(
|
|
reservation=self.reservation,
|
|
product=self.product
|
|
)
|
|
|
|
self.assertEqual(item.quantity, 1)
|
|
|
|
def test_reservation_item_unique_together(self):
|
|
"""No pueden haber dos items del mismo producto en la misma reserva."""
|
|
StockReservationItem.objects.create(
|
|
reservation=self.reservation,
|
|
product=self.product,
|
|
quantity=5
|
|
)
|
|
|
|
with self.assertRaises(IntegrityError):
|
|
StockReservationItem.objects.create(
|
|
reservation=self.reservation,
|
|
product=self.product,
|
|
quantity=3
|
|
)
|
|
|
|
def test_reservation_item_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
item = StockReservationItem.objects.create(
|
|
reservation=self.reservation,
|
|
product=self.product,
|
|
quantity=5
|
|
)
|
|
|
|
item_str = str(item)
|
|
self.assertIn("5x", item_str)
|
|
self.assertIn("TestProd", item_str)
|
|
|
|
|
|
# ==================== CART MODEL TESTS ====================
|
|
class CartModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo Cart."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="cartuser")
|
|
self.category = Category.objects.create(name="TestCat")
|
|
self.product1 = Product.objects.create(
|
|
name="Product1", price=100, category=self.category
|
|
)
|
|
self.product2 = Product.objects.create(
|
|
name="Product2", price=50, category=self.category
|
|
)
|
|
|
|
def test_cart_creation_with_user(self):
|
|
"""Carrito debe crearse para usuario autenticado."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
self.assertEqual(cart.user, self.user)
|
|
self.assertIsNone(cart.session_key)
|
|
|
|
def test_cart_creation_with_session(self):
|
|
"""Carrito debe crearse para sesión anónima."""
|
|
cart = Cart.objects.create(session_key="session123")
|
|
self.assertEqual(cart.session_key, "session123")
|
|
self.assertIsNone(cart.user)
|
|
|
|
def test_cart_timestamps(self):
|
|
"""Los timestamps deben establecerse automáticamente."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
self.assertIsNotNone(cart.created_at)
|
|
self.assertIsNotNone(cart.updated_at)
|
|
|
|
def test_cart_get_total_empty(self):
|
|
"""Total de carrito vacío debe ser 0."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
self.assertEqual(cart.get_total(), 0)
|
|
|
|
def test_cart_get_total_with_items(self):
|
|
"""Total debe calcularse correctamente."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
CartItem.objects.create(cart=cart, product=self.product1, quantity=2) # 200
|
|
CartItem.objects.create(cart=cart, product=self.product2, quantity=1) # 50
|
|
|
|
self.assertEqual(cart.get_total(), 250)
|
|
|
|
def test_cart_get_total_with_vat(self):
|
|
"""Total con IVA debe ser correcto."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
CartItem.objects.create(cart=cart, product=self.product1, quantity=1) # 100
|
|
|
|
expected = round(100 * (1 + VAT_RATE), 2)
|
|
self.assertEqual(cart.get_total_with_vat(), expected)
|
|
|
|
def test_cart_get_vat_amount(self):
|
|
"""Cantidad de IVA debe calcularse correctamente."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
CartItem.objects.create(cart=cart, product=self.product1, quantity=1) # 100
|
|
|
|
expected = round(100 * VAT_RATE, 2)
|
|
self.assertEqual(cart.get_vat_amount(), expected)
|
|
|
|
def test_cart_get_items_count(self):
|
|
"""Conteo de items debe ser correcto."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
self.assertEqual(cart.get_items_count(), 0)
|
|
|
|
CartItem.objects.create(cart=cart, product=self.product1, quantity=2)
|
|
CartItem.objects.create(cart=cart, product=self.product2, quantity=3)
|
|
|
|
self.assertEqual(cart.get_items_count(), 5)
|
|
|
|
def test_cart_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
cart = Cart.objects.create(user=self.user)
|
|
self.assertIn("Cart", str(cart))
|
|
|
|
|
|
# ==================== CART ITEM MODEL TESTS ====================
|
|
class CartItemModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo CartItem."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="cartuser")
|
|
self.category = Category.objects.create(name="TestCat")
|
|
self.product = Product.objects.create(
|
|
name="TestProduct", price=50, category=self.category
|
|
)
|
|
self.cart = Cart.objects.create(user=self.user)
|
|
|
|
def test_cart_item_creation(self):
|
|
"""Item del carrito debe crearse correctamente."""
|
|
item = CartItem.objects.create(
|
|
cart=self.cart,
|
|
product=self.product,
|
|
quantity=5
|
|
)
|
|
|
|
self.assertEqual(item.quantity, 5)
|
|
self.assertEqual(item.product, self.product)
|
|
|
|
def test_cart_item_default_quantity(self):
|
|
"""Cantidad por defecto es 1."""
|
|
item = CartItem.objects.create(cart=self.cart, product=self.product)
|
|
self.assertEqual(item.quantity, 1)
|
|
|
|
def test_cart_item_unique_together(self):
|
|
"""No pueden haber dos items del mismo producto en el mismo carrito."""
|
|
CartItem.objects.create(cart=self.cart, product=self.product, quantity=5)
|
|
|
|
with self.assertRaises(IntegrityError):
|
|
CartItem.objects.create(cart=self.cart, product=self.product, quantity=3)
|
|
|
|
def test_cart_item_get_subtotal(self):
|
|
"""Subtotal debe calcularse correctamente."""
|
|
item = CartItem.objects.create(
|
|
cart=self.cart,
|
|
product=self.product,
|
|
quantity=3
|
|
)
|
|
|
|
self.assertEqual(item.get_subtotal(), 150)
|
|
|
|
def test_cart_item_get_subtotal_with_vat(self):
|
|
"""Subtotal con IVA debe ser correcto."""
|
|
item = CartItem.objects.create(
|
|
cart=self.cart,
|
|
product=self.product,
|
|
quantity=2
|
|
)
|
|
|
|
expected = round(100 * (1 + VAT_RATE), 2)
|
|
self.assertEqual(item.get_subtotal_with_vat(), expected)
|
|
|
|
def test_cart_item_get_vat_amount(self):
|
|
"""Cantidad de IVA del item debe ser correcta."""
|
|
item = CartItem.objects.create(
|
|
cart=self.cart,
|
|
product=self.product,
|
|
quantity=2
|
|
)
|
|
|
|
expected = round(100 * VAT_RATE, 2)
|
|
self.assertEqual(item.get_vat_amount(), expected)
|
|
|
|
def test_cart_item_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
item = CartItem.objects.create(
|
|
cart=self.cart,
|
|
product=self.product,
|
|
quantity=3
|
|
)
|
|
|
|
self.assertEqual(str(item), "3x TestProduct")
|
|
|
|
|
|
# ==================== ORDER MODEL TESTS ====================
|
|
class OrderModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo Order."""
|
|
|
|
def setUp(self):
|
|
self.buyer = User.objects.create(username="buyer")
|
|
self.address = ShippingAddress.objects.create(
|
|
user=self.buyer,
|
|
full_name="John Doe",
|
|
address_line_1="123 Main St",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
country="España",
|
|
phone="123456789"
|
|
)
|
|
|
|
def test_order_creation_full(self):
|
|
"""Pedido debe crearse con todos los campos."""
|
|
order = Order.objects.create(
|
|
buyer=self.buyer,
|
|
shipping_address=self.address,
|
|
total=150.50,
|
|
status=Order.STATUS_PAID,
|
|
payment_method=Order.PAYMENT_STRIPE
|
|
)
|
|
|
|
self.assertEqual(order.buyer, self.buyer)
|
|
self.assertEqual(order.total, 150.50)
|
|
self.assertEqual(order.status, Order.STATUS_PAID)
|
|
|
|
def test_order_transaction_code_auto_generated(self):
|
|
"""Código de transacción debe generarse automáticamente al guardar."""
|
|
order = Order.objects.create(
|
|
buyer=self.buyer,
|
|
status=Order.STATUS_PAID
|
|
)
|
|
|
|
self.assertIsNotNone(order.transaction_code)
|
|
self.assertTrue(order.transaction_code.startswith(TRANSACTION_CODE_PREFIX))
|
|
|
|
def test_order_transaction_code_unique(self):
|
|
"""Códigos de transacción deben ser únicos."""
|
|
order1 = Order.objects.create(
|
|
buyer=self.buyer,
|
|
status=Order.STATUS_PAID
|
|
)
|
|
order2 = Order.objects.create(
|
|
buyer=self.buyer,
|
|
status=Order.STATUS_PAID
|
|
)
|
|
|
|
self.assertNotEqual(order1.transaction_code, order2.transaction_code)
|
|
|
|
def test_order_default_status(self):
|
|
"""Estado por defecto es PAID."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
self.assertEqual(order.status, Order.STATUS_PAID)
|
|
|
|
def test_order_default_payment_method(self):
|
|
"""Método de pago por defecto es MANUAL."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
self.assertEqual(order.payment_method, Order.PAYMENT_MANUAL)
|
|
|
|
def test_order_status_choices(self):
|
|
"""Todos los estados deben ser válidos."""
|
|
for i, status in enumerate([Order.STATUS_PAID, Order.STATUS_CANCELLED]):
|
|
order = Order.objects.create(
|
|
buyer=self.buyer,
|
|
status=status
|
|
)
|
|
self.assertEqual(order.status, status)
|
|
|
|
def test_order_payment_methods(self):
|
|
"""Todos los métodos de pago deben ser válidos."""
|
|
methods = [Order.PAYMENT_STRIPE, Order.PAYMENT_PAYPAL, Order.PAYMENT_MANUAL]
|
|
for method in methods:
|
|
order = Order.objects.create(
|
|
buyer=self.buyer,
|
|
payment_method=method
|
|
)
|
|
self.assertEqual(order.payment_method, method)
|
|
|
|
def test_order_anonymous_buyer(self):
|
|
"""Pedido puede tener buyer nulo (comprador anónimo)."""
|
|
order = Order.objects.create(
|
|
buyer=None,
|
|
session_key="session123",
|
|
status=Order.STATUS_PAID
|
|
)
|
|
self.assertIsNone(order.buyer)
|
|
|
|
def test_order_payment_reference_optional(self):
|
|
"""Referencia de pago es opcional."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
self.assertEqual(order.payment_reference, "")
|
|
|
|
def test_order_timestamps(self):
|
|
"""Los timestamps deben establecerse automáticamente."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
self.assertIsNotNone(order.created_at)
|
|
self.assertIsNotNone(order.updated_at)
|
|
|
|
def test_order_get_items_count_empty(self):
|
|
"""Conteo de items en pedido vacío debe ser 0."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
self.assertEqual(order.get_items_count(), 0)
|
|
|
|
def test_order_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
order = Order.objects.create(buyer=self.buyer)
|
|
order_str = str(order)
|
|
self.assertIn("Pedido", order_str)
|
|
|
|
|
|
# ==================== ORDER ITEM MODEL TESTS ====================
|
|
class OrderItemModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo OrderItem."""
|
|
|
|
def setUp(self):
|
|
self.buyer = User.objects.create(username="buyer")
|
|
self.seller = User.objects.create(username="seller")
|
|
self.category = Category.objects.create(name="TestCat")
|
|
self.product = Product.objects.create(
|
|
name="TestProduct",
|
|
price=100,
|
|
category=self.category,
|
|
creator=self.seller
|
|
)
|
|
self.order = Order.objects.create(buyer=self.buyer)
|
|
|
|
def test_order_item_creation_full(self):
|
|
"""Item de pedido debe crearse correctamente."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product=self.product,
|
|
product_name="TestProduct",
|
|
seller=self.seller,
|
|
quantity=5,
|
|
unit_price=100,
|
|
total_price=500,
|
|
status=OrderItem.STATUS_PENDING
|
|
)
|
|
|
|
self.assertEqual(item.quantity, 5)
|
|
self.assertEqual(item.unit_price, 100)
|
|
self.assertEqual(item.total_price, 500)
|
|
|
|
def test_order_item_status_choices(self):
|
|
"""Todos los estados deben ser válidos."""
|
|
statuses = [
|
|
OrderItem.STATUS_PENDING,
|
|
OrderItem.STATUS_PROCESSING,
|
|
OrderItem.STATUS_SHIPPED
|
|
]
|
|
|
|
for status in statuses:
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product=self.product,
|
|
product_name="Test",
|
|
status=status
|
|
)
|
|
self.assertEqual(item.status, status)
|
|
|
|
def test_order_item_default_status(self):
|
|
"""Estado por defecto es PENDING."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product_name="Test"
|
|
)
|
|
self.assertEqual(item.status, OrderItem.STATUS_PENDING)
|
|
|
|
def test_order_item_product_optional(self):
|
|
"""Producto puede ser nulo (producto eliminado)."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product=None,
|
|
product_name="Deleted Product"
|
|
)
|
|
self.assertIsNone(item.product)
|
|
|
|
def test_order_item_seller_optional(self):
|
|
"""Vendedor puede ser nulo."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product_name="Test",
|
|
seller=None
|
|
)
|
|
self.assertIsNone(item.seller)
|
|
|
|
def test_order_item_timestamps(self):
|
|
"""El timestamp debe establecerse automáticamente."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product_name="Test"
|
|
)
|
|
self.assertIsNotNone(item.created_at)
|
|
|
|
def test_order_item_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product_name="TestProduct",
|
|
quantity=3
|
|
)
|
|
|
|
item_str = str(item)
|
|
self.assertIn("3x", item_str)
|
|
self.assertIn("TestProduct", item_str)
|
|
|
|
|
|
# ==================== ORDER MESSAGE MODEL TESTS ====================
|
|
class OrderMessageModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo OrderMessage."""
|
|
|
|
def setUp(self):
|
|
self.buyer = User.objects.create(username="buyer")
|
|
self.seller = User.objects.create(username="seller")
|
|
self.order = Order.objects.create(buyer=self.buyer)
|
|
self.order_item = OrderItem.objects.create(
|
|
order=self.order,
|
|
product_name="Test",
|
|
seller=self.seller
|
|
)
|
|
|
|
def test_order_message_creation(self):
|
|
"""Mensaje debe crearse correctamente."""
|
|
message = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=self.buyer,
|
|
message="Hello seller!"
|
|
)
|
|
|
|
self.assertEqual(message.message, "Hello seller!")
|
|
self.assertEqual(message.sender, self.buyer)
|
|
|
|
def test_order_message_sender_optional(self):
|
|
"""Remitente puede ser nulo."""
|
|
message = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=None,
|
|
message="Anonymous message"
|
|
)
|
|
self.assertIsNone(message.sender)
|
|
|
|
def test_order_message_timestamp(self):
|
|
"""El timestamp debe establecerse automáticamente."""
|
|
message = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=self.buyer,
|
|
message="Test"
|
|
)
|
|
self.assertIsNotNone(message.created_at)
|
|
|
|
def test_order_message_ordering(self):
|
|
"""Los mensajes deben ordenarse por created_at."""
|
|
msg1 = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=self.buyer,
|
|
message="First"
|
|
)
|
|
msg2 = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=self.seller,
|
|
message="Second"
|
|
)
|
|
|
|
messages = list(self.order_item.messages.all())
|
|
self.assertEqual(messages[0].message, "First")
|
|
self.assertEqual(messages[1].message, "Second")
|
|
|
|
def test_order_message_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
message = OrderMessage.objects.create(
|
|
order_item=self.order_item,
|
|
sender=self.buyer,
|
|
message="Test message"
|
|
)
|
|
|
|
message_str = str(message)
|
|
self.assertIn("buyer", message_str)
|
|
|
|
|
|
# ==================== SAVED PAYMENT METHOD MODEL TESTS ====================
|
|
class SavedPaymentMethodModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo SavedPaymentMethod."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="paymentuser")
|
|
|
|
def test_saved_payment_method_card_creation(self):
|
|
"""Método de pago tarjeta debe crearse correctamente."""
|
|
method = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Mi Tarjeta",
|
|
stripe_customer_id="cus_123",
|
|
stripe_payment_method_id="pm_456"
|
|
)
|
|
|
|
self.assertEqual(method.method_type, SavedPaymentMethod.TYPE_CARD)
|
|
self.assertEqual(method.label, "Mi Tarjeta")
|
|
|
|
def test_saved_payment_method_paypal_creation(self):
|
|
"""Método de pago PayPal debe crearse correctamente."""
|
|
method = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_PAYPAL,
|
|
label="Mi PayPal",
|
|
paypal_email="user@example.com",
|
|
paypal_payer_id="ABC123"
|
|
)
|
|
|
|
self.assertEqual(method.method_type, SavedPaymentMethod.TYPE_PAYPAL)
|
|
self.assertEqual(method.paypal_email, "user@example.com")
|
|
|
|
def test_saved_payment_method_default_false(self):
|
|
"""Por defecto no es predeterminado."""
|
|
method = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Test"
|
|
)
|
|
|
|
self.assertFalse(method.is_default)
|
|
|
|
def test_saved_payment_method_set_default_unsets_others(self):
|
|
"""Marcar como predeterminado debe desmarcar otros."""
|
|
method1 = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Card1",
|
|
is_default=True
|
|
)
|
|
|
|
method2 = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Card2",
|
|
is_default=True
|
|
)
|
|
|
|
method1.refresh_from_db()
|
|
self.assertFalse(method1.is_default)
|
|
self.assertTrue(method2.is_default)
|
|
|
|
def test_saved_payment_method_ordering(self):
|
|
"""Los métodos deben ordenarse por predeterminado y fecha."""
|
|
method2 = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Card2",
|
|
is_default=False
|
|
)
|
|
|
|
method1 = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Card1",
|
|
is_default=True
|
|
)
|
|
|
|
methods = list(SavedPaymentMethod.objects.filter(user=self.user))
|
|
self.assertEqual(methods[0].id, method1.id)
|
|
|
|
def test_saved_payment_method_timestamps(self):
|
|
"""El timestamp debe establecerse automáticamente."""
|
|
method = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="Test"
|
|
)
|
|
self.assertIsNotNone(method.created_at)
|
|
|
|
def test_saved_payment_method_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
method = SavedPaymentMethod.objects.create(
|
|
user=self.user,
|
|
method_type=SavedPaymentMethod.TYPE_CARD,
|
|
label="My Card"
|
|
)
|
|
|
|
method_str = str(method)
|
|
self.assertIn("paymentuser", method_str)
|
|
self.assertIn("My Card", method_str)
|
|
|
|
|
|
# ==================== SHIPPING ADDRESS MODEL TESTS ====================
|
|
class ShippingAddressModelTests(TestCase):
|
|
"""Tests exhaustivos para el modelo ShippingAddress."""
|
|
|
|
def setUp(self):
|
|
self.user = User.objects.create(username="addressuser")
|
|
|
|
def test_shipping_address_creation_full(self):
|
|
"""Dirección de envío debe crearse correctamente."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="John Doe",
|
|
address_line_1="123 Main St",
|
|
address_line_2="Apt 4B",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
country="España",
|
|
phone="123456789"
|
|
)
|
|
|
|
self.assertEqual(address.full_name, "John Doe")
|
|
self.assertEqual(address.city, "Almería")
|
|
|
|
def test_shipping_address_default_country(self):
|
|
"""País por defecto es España."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Test",
|
|
address_line_1="Test St",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="123456789"
|
|
)
|
|
|
|
self.assertEqual(address.country, "España")
|
|
|
|
def test_shipping_address_line_2_optional(self):
|
|
"""Línea de dirección 2 es opcional."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Test",
|
|
address_line_1="Test St",
|
|
address_line_2="",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="123456789"
|
|
)
|
|
|
|
self.assertEqual(address.address_line_2, "")
|
|
|
|
def test_shipping_address_is_default_false(self):
|
|
"""Por defecto no es dirección predeterminada."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Test",
|
|
address_line_1="Test St",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="123456789"
|
|
)
|
|
|
|
self.assertFalse(address.is_default)
|
|
|
|
def test_shipping_address_set_default_unsets_others(self):
|
|
"""Marcar como predeterminada debe desmarcar otras."""
|
|
addr1 = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Address1",
|
|
address_line_1="St1",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="1",
|
|
is_default=True
|
|
)
|
|
|
|
addr2 = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Address2",
|
|
address_line_1="St2",
|
|
city="Almería",
|
|
postal_code="04002",
|
|
phone="2",
|
|
is_default=True
|
|
)
|
|
|
|
addr1.refresh_from_db()
|
|
self.assertFalse(addr1.is_default)
|
|
self.assertTrue(addr2.is_default)
|
|
|
|
def test_shipping_address_ordering(self):
|
|
"""Las direcciones deben ordenarse por predeterminada y fecha."""
|
|
addr2 = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Address2",
|
|
address_line_1="St2",
|
|
city="Almería",
|
|
postal_code="04002",
|
|
phone="2",
|
|
is_default=False
|
|
)
|
|
|
|
addr1 = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Address1",
|
|
address_line_1="St1",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="1",
|
|
is_default=True
|
|
)
|
|
|
|
addresses = list(ShippingAddress.objects.filter(user=self.user))
|
|
self.assertEqual(addresses[0].id, addr1.id)
|
|
|
|
def test_shipping_address_timestamps(self):
|
|
"""Los timestamps deben establecerse automáticamente."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="Test",
|
|
address_line_1="Test St",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="123456789"
|
|
)
|
|
|
|
self.assertIsNotNone(address.created_at)
|
|
self.assertIsNotNone(address.updated_at)
|
|
|
|
def test_shipping_address_str_representation(self):
|
|
"""La representación string debe ser válida."""
|
|
address = ShippingAddress.objects.create(
|
|
user=self.user,
|
|
full_name="John Doe",
|
|
address_line_1="123 Main St",
|
|
city="Almería",
|
|
postal_code="04001",
|
|
phone="123456789"
|
|
)
|
|
|
|
address_str = str(address)
|
|
self.assertIn("John Doe", address_str)
|
|
self.assertIn("Almería", address_str) |