Fix: Rename directory to remove & character causing shell issues
Renamed ebook_backend&admin_panel to ebook_backend_admin_panel The & character was being interpreted by shell as background process operator, causing 'Dockerfile not found' errors in Coolify.
This commit is contained in:
480
ebook_backend_admin_panel/admin-backend/tests/test_models.py
Normal file
480
ebook_backend_admin_panel/admin-backend/tests/test_models.py
Normal file
@@ -0,0 +1,480 @@
|
||||
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
|
||||
Reference in New Issue
Block a user