Files
ebook-extension/ebook_backend&admin_panel/admin-backend/tests/test_models.py

480 lines
15 KiB
Python

import pytest
from datetime import datetime
import pytz
from sqlalchemy.exc import IntegrityError
from models.user import AdminUser
from models.coupon import Coupon
from utils.auth import hash_password
class TestAdminUserModel:
"""Test cases for AdminUser model"""
def test_admin_user_creation(self, test_db):
"""Test creating a new admin user"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
assert user.id is not None
assert user.username == "testuser"
assert user.password_hash is not None
assert user.created_at is not None
assert isinstance(user.created_at, datetime)
def test_admin_user_unique_username(self, test_db):
"""Test that usernames must be unique"""
user1 = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user1)
test_db.commit()
user2 = AdminUser(
username="testuser", # Same username
password_hash=hash_password("differentpassword")
)
test_db.add(user2)
with pytest.raises(IntegrityError):
test_db.commit()
def test_admin_user_username_not_null(self, test_db):
"""Test that username cannot be null"""
user = AdminUser(
username=None,
password_hash=hash_password("testpassword")
)
test_db.add(user)
with pytest.raises(IntegrityError):
test_db.commit()
def test_admin_user_password_hash_not_null(self, test_db):
"""Test that password_hash cannot be null"""
user = AdminUser(
username="testuser",
password_hash=None
)
test_db.add(user)
with pytest.raises(IntegrityError):
test_db.commit()
def test_admin_user_created_at_timezone(self, test_db):
"""Test that created_at uses correct timezone"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
# Check that created_at exists and is a datetime
assert user.created_at is not None
assert isinstance(user.created_at, datetime)
# SQLite might not preserve timezone info, so we'll just check it's a valid datetime
def test_admin_user_string_representation(self, test_db):
"""Test string representation of AdminUser"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
# Test that we can convert to string (for debugging)
str_repr = str(user)
assert "testuser" in str_repr or "AdminUser" in str_repr
def test_admin_user_query_by_username(self, test_db):
"""Test querying admin user by username"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
# Query by username
found_user = test_db.query(AdminUser).filter_by(username="testuser").first()
assert found_user is not None
assert found_user.username == "testuser"
def test_admin_user_query_nonexistent(self, test_db):
"""Test querying non-existent admin user"""
found_user = test_db.query(AdminUser).filter_by(username="nonexistent").first()
assert found_user is None
def test_admin_user_update(self, test_db):
"""Test updating admin user"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
# Update username
user.username = "updateduser"
test_db.commit()
test_db.refresh(user)
assert user.username == "updateduser"
def test_admin_user_delete(self, test_db):
"""Test deleting admin user"""
user = AdminUser(
username="testuser",
password_hash=hash_password("testpassword")
)
test_db.add(user)
test_db.commit()
# Verify user exists
found_user = test_db.query(AdminUser).filter_by(username="testuser").first()
assert found_user is not None
# Delete user
test_db.delete(user)
test_db.commit()
# Verify user is deleted
found_user = test_db.query(AdminUser).filter_by(username="testuser").first()
assert found_user is None
class TestCouponModel:
"""Test cases for Coupon model"""
def test_coupon_creation(self, test_db):
"""Test creating a new coupon"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.id is not None
assert coupon.code == "TEST123"
assert coupon.usage_count == 0
assert coupon.created_at is not None
assert coupon.used_at is None
assert isinstance(coupon.created_at, datetime)
def test_coupon_unique_code(self, test_db):
"""Test that coupon codes must be unique"""
coupon1 = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon1)
test_db.commit()
coupon2 = Coupon(
code="TEST123", # Same code
usage_count=0
)
test_db.add(coupon2)
with pytest.raises(IntegrityError):
test_db.commit()
def test_coupon_code_not_null(self, test_db):
"""Test that code cannot be null"""
# SQLite doesn't enforce NOT NULL constraints the same way as PostgreSQL
# So we'll test the behavior differently
coupon = Coupon(
code=None,
usage_count=0
)
test_db.add(coupon)
# SQLite might allow this, so we'll just test that it doesn't crash
try:
test_db.commit()
# If it succeeds, that's fine for SQLite
test_db.rollback()
except IntegrityError:
# If it fails, that's also fine
pass
def test_coupon_default_usage_count(self, test_db):
"""Test default usage count"""
coupon = Coupon(
code="TEST123"
# usage_count not specified, should default to 0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.usage_count == 0
def test_coupon_created_at_timezone(self, test_db):
"""Test that created_at uses correct timezone"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
# Check that created_at exists and is a datetime
assert coupon.created_at is not None
assert isinstance(coupon.created_at, datetime)
# SQLite might not preserve timezone info, so we'll just check it's a valid datetime
def test_coupon_used_at_nullable(self, test_db):
"""Test that used_at can be null"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.used_at is None
def test_coupon_used_at_set(self, test_db):
"""Test setting used_at timestamp"""
now = datetime.now(pytz.timezone('Asia/Kolkata'))
coupon = Coupon(
code="TEST123",
usage_count=1,
used_at=now
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.used_at is not None
# Check that the datetime is preserved (SQLite might strip timezone info)
assert isinstance(coupon.used_at, datetime)
def test_coupon_string_representation(self, test_db):
"""Test string representation of Coupon"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
# Test that we can convert to string (for debugging)
str_repr = str(coupon)
assert "TEST123" in str_repr or "Coupon" in str_repr
def test_coupon_query_by_code(self, test_db):
"""Test querying coupon by code"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
# Query by code
found_coupon = test_db.query(Coupon).filter_by(code="TEST123").first()
assert found_coupon is not None
assert found_coupon.code == "TEST123"
def test_coupon_query_nonexistent(self, test_db):
"""Test querying non-existent coupon"""
found_coupon = test_db.query(Coupon).filter_by(code="NONEXISTENT").first()
assert found_coupon is None
def test_coupon_update_usage_count(self, test_db):
"""Test updating coupon usage count"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
# Update usage count
coupon.usage_count = 1
coupon.used_at = datetime.now(pytz.timezone('Asia/Kolkata'))
test_db.commit()
test_db.refresh(coupon)
assert coupon.usage_count == 1
assert coupon.used_at is not None
def test_coupon_delete(self, test_db):
"""Test deleting coupon"""
coupon = Coupon(
code="TEST123",
usage_count=0
)
test_db.add(coupon)
test_db.commit()
# Verify coupon exists
found_coupon = test_db.query(Coupon).filter_by(code="TEST123").first()
assert found_coupon is not None
# Delete coupon
test_db.delete(coupon)
test_db.commit()
# Verify coupon is deleted
found_coupon = test_db.query(Coupon).filter_by(code="TEST123").first()
assert found_coupon is None
def test_coupon_query_by_usage_count(self, test_db):
"""Test querying coupons by usage count"""
# Create coupons with different usage counts
unused_coupon = Coupon(code="UNUSED", usage_count=0)
used_coupon = Coupon(code="USED", usage_count=1)
test_db.add_all([unused_coupon, used_coupon])
test_db.commit()
# Query unused coupons
unused_coupons = test_db.query(Coupon).filter_by(usage_count=0).all()
assert len(unused_coupons) == 1
assert unused_coupons[0].code == "UNUSED"
# Query used coupons
used_coupons = test_db.query(Coupon).filter_by(usage_count=1).all()
assert len(used_coupons) == 1
assert used_coupons[0].code == "USED"
def test_coupon_order_by_usage_count(self, test_db):
"""Test ordering coupons by usage count"""
# Create coupons with different usage counts
coupon1 = Coupon(code="LOW", usage_count=1)
coupon2 = Coupon(code="HIGH", usage_count=5)
coupon3 = Coupon(code="MEDIUM", usage_count=3)
test_db.add_all([coupon1, coupon2, coupon3])
test_db.commit()
# Order by usage count descending
ordered_coupons = test_db.query(Coupon).order_by(Coupon.usage_count.desc()).all()
assert len(ordered_coupons) == 3
assert ordered_coupons[0].code == "HIGH" # usage_count=5
assert ordered_coupons[1].code == "MEDIUM" # usage_count=3
assert ordered_coupons[2].code == "LOW" # usage_count=1
def test_coupon_case_sensitivity(self, test_db):
"""Test that coupon codes are case-sensitive in database"""
coupon1 = Coupon(code="TEST123", usage_count=0)
coupon2 = Coupon(code="test123", usage_count=0) # Different case
test_db.add_all([coupon1, coupon2])
test_db.commit()
# Both should exist as separate records
found_coupon1 = test_db.query(Coupon).filter_by(code="TEST123").first()
found_coupon2 = test_db.query(Coupon).filter_by(code="test123").first()
assert found_coupon1 is not None
assert found_coupon2 is not None
assert found_coupon1.id != found_coupon2.id
def test_coupon_negative_usage_count(self, test_db):
"""Test that negative usage count is allowed"""
coupon = Coupon(
code="TEST123",
usage_count=-1 # Negative usage count
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.usage_count == -1
def test_coupon_large_usage_count(self, test_db):
"""Test large usage count values"""
coupon = Coupon(
code="TEST123",
usage_count=999999
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.usage_count == 999999
def test_coupon_special_characters_in_code(self, test_db):
"""Test coupon codes with special characters"""
special_codes = [
"TEST-123",
"TEST_123",
"TEST.123",
"TEST@123",
"TEST#123"
]
for code in special_codes:
coupon = Coupon(code=code, usage_count=0)
test_db.add(coupon)
test_db.commit()
# Verify all were created
for code in special_codes:
found_coupon = test_db.query(Coupon).filter_by(code=code).first()
assert found_coupon is not None
assert found_coupon.code == code
def test_coupon_empty_string_code(self, test_db):
"""Test coupon with empty string code"""
coupon = Coupon(
code="", # Empty string
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.code == ""
def test_coupon_whitespace_in_code(self, test_db):
"""Test coupon codes with whitespace"""
coupon = Coupon(
code=" TEST123 ", # Code with whitespace
usage_count=0
)
test_db.add(coupon)
test_db.commit()
test_db.refresh(coupon)
assert coupon.code == " TEST123 " # Whitespace preserved