Files
CMT/backend/tests/integration/test_models.py
T
linmihigo c086f64363 feat: implement complete CMT backend with API endpoints and test suite
- Add 7 core API endpoints: users, transactions, partners, products, inventory, payments, credit
- Implement role-based authentication (admin/write/read-only access)
- Add comprehensive database models with proper relationships
- Include full test coverage for all endpoints and business logic
- Set up Alembic migrations and Docker configuration
- Configure FastAPI app with CORS and database integration
2025-09-14 21:04:07 +02:00

367 lines
13 KiB
Python

"""Integration tests for SQLModel database operations."""
import pytest
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from app.schemas.models import (
User, Partner, Product, Transaction,
Transaction_details, Inventory, Payment, Credit
)
from app.schemas.base import (
UserRole, PartnerType, TransactionType,
TransactionStatus, PaymentMethod
)
class TestUserModel:
"""Test User model database operations."""
def test_user_creation_and_retrieval(self, integration_session: Session):
"""Test creating and retrieving users from database."""
user = User(username="testuser", password_hash="hashed", role=UserRole.ADMIN)
integration_session.add(user)
integration_session.commit()
integration_session.refresh(user)
# Verify user was created with ID
assert user.id is not None
assert user.username == "testuser"
assert user.role == UserRole.ADMIN
# Verify retrieval from database
retrieved_user = integration_session.get(User, user.id)
assert retrieved_user is not None
assert retrieved_user.username == "testuser"
def test_user_unique_username_constraint(self, integration_session: Session):
"""Test that duplicate usernames are rejected."""
user1 = User(username="duplicate", password_hash="hash1", role=UserRole.ADMIN)
user2 = User(username="duplicate", password_hash="hash2", role=UserRole.READ_ONLY)
integration_session.add(user1)
integration_session.commit()
integration_session.add(user2)
with pytest.raises(IntegrityError):
integration_session.commit()
def test_user_role_defaults(self, integration_session: Session):
"""Test user role default values."""
user = User(username="defaultrole", password_hash="hash")
integration_session.add(user)
integration_session.commit()
integration_session.refresh(user)
# Check default role is READ_ONLY
assert user.role == UserRole.READ_ONLY
class TestPartnerModel:
"""Test Partner model database operations."""
def test_partner_creation_and_types(self, integration_session: Session):
"""Test creating partners with different types."""
partners = [
Partner(tin_number=123456789, names="Client Partner", type=PartnerType.CLIENT, phone_number="1234567890"),
Partner(tin_number=987654321, names="Supplier Partner", type=PartnerType.SUPPLIER, phone_number="0987654321"),
]
for partner in partners:
integration_session.add(partner)
integration_session.commit()
# Verify both partners were created
client_partner = integration_session.exec(
select(Partner).where(Partner.type == PartnerType.CLIENT)
).first()
supplier_partner = integration_session.exec(
select(Partner).where(Partner.type == PartnerType.SUPPLIER)
).first()
assert client_partner is not None
assert supplier_partner is not None
assert client_partner.names == "Client Partner"
assert supplier_partner.names == "Supplier Partner"
def test_partner_unique_tin_constraint(self, integration_session: Session):
"""Test that duplicate TIN numbers are rejected."""
partner1 = Partner(tin_number=123456789, names="Partner 1", type=PartnerType.CLIENT, phone_number="1234567890")
partner2 = Partner(tin_number=123456789, names="Partner 2", type=PartnerType.SUPPLIER, phone_number="0987654321")
integration_session.add(partner1)
integration_session.commit()
integration_session.add(partner2)
with pytest.raises(IntegrityError):
integration_session.commit()
class TestProductModel:
"""Test Product model database operations."""
def test_product_creation(self, integration_session: Session):
"""Test basic product creation."""
product = Product(
product_code="TEST001",
product_name="Test Product",
purchase_price=100,
selling_price=120
)
integration_session.add(product)
integration_session.commit()
integration_session.refresh(product)
assert product.id is not None
assert product.product_name == "Test Product"
assert product.product_code == "TEST001"
def test_product_unique_name_constraint(self, integration_session: Session):
"""Test that duplicate product names are rejected."""
product1 = Product(
product_code="DUP001",
product_name="Duplicate Product",
purchase_price=100,
selling_price=120
)
product2 = Product(
product_code="DUP002",
product_name="Duplicate Product", # Same name, different code
purchase_price=150,
selling_price=180
)
integration_session.add(product1)
integration_session.commit()
integration_session.add(product2)
with pytest.raises(IntegrityError):
integration_session.commit()
class TestTransactionModel:
"""Test Transaction model with relationships."""
def test_transaction_creation(self, integration_session: Session):
"""Test creating transaction with valid relationships."""
# Create required entities
user = User(username="trans_user", password_hash="hash", role=UserRole.ADMIN)
partner = Partner(
tin_number=123456789,
names="Transaction Partner",
type=PartnerType.CLIENT,
phone_number="1234567890"
)
integration_session.add(user)
integration_session.add(partner)
integration_session.commit()
integration_session.refresh(user)
integration_session.refresh(partner)
# Create transaction - use type assertion for nullable IDs
transaction = Transaction(
partner_id=partner.id, # type: ignore
transcation_type=TransactionType.SALE,
transaction_status=TransactionStatus.UNPAID,
total_amount=500,
created_by=user.id, # type: ignore
updated_by=user.id # type: ignore
)
integration_session.add(transaction)
integration_session.commit()
integration_session.refresh(transaction)
assert transaction.id is not None
assert transaction.partner_id == partner.id
assert transaction.total_amount == 500
class TestInventoryModel:
"""Test Inventory model operations."""
def test_inventory_creation(self, integration_session: Session):
"""Test creating inventory with valid product reference."""
# Create product first
product = Product(
product_code="INV001",
product_name="Inventory Product",
purchase_price=100,
selling_price=120
)
integration_session.add(product)
integration_session.commit()
integration_session.refresh(product)
# Create inventory
inventory = Inventory(
product_id=product.id, # type: ignore
total_qty=100
)
integration_session.add(inventory)
integration_session.commit()
integration_session.refresh(inventory)
assert inventory.id is not None
assert inventory.product_id == product.id
assert inventory.total_qty == 100
def test_inventory_unique_product_constraint(self, integration_session: Session):
"""Test that each product can only have one inventory record."""
product = Product(
product_code="SINGLE",
product_name="Single Inventory",
purchase_price=100,
selling_price=120
)
integration_session.add(product)
integration_session.commit()
integration_session.refresh(product)
inventory1 = Inventory(
product_id=product.id, # type: ignore
total_qty=50
)
inventory2 = Inventory(
product_id=product.id, # type: ignore
total_qty=100
)
integration_session.add(inventory1)
integration_session.commit()
integration_session.add(inventory2)
with pytest.raises(IntegrityError):
integration_session.commit()
class TestCreditModel:
"""Test Credit model operations."""
def test_credit_creation(self, integration_session: Session):
"""Test creating credit with valid partner and transaction reference."""
# Create partner, user, and transaction
partner = Partner(
tin_number=123456789,
names="Credit Partner",
type=PartnerType.CLIENT,
phone_number="1234567890"
)
user = User(username="credit_user", password_hash="hash", role=UserRole.ADMIN)
integration_session.add(partner)
integration_session.add(user)
integration_session.commit()
integration_session.refresh(partner)
integration_session.refresh(user)
# Create a transaction for the credit
transaction = Transaction(
partner_id=partner.id, # type: ignore
transcation_type=TransactionType.SALE,
transaction_status=TransactionStatus.UNPAID,
total_amount=1000,
created_by=user.id, # type: ignore
updated_by=user.id # type: ignore
)
integration_session.add(transaction)
integration_session.commit()
integration_session.refresh(transaction)
# Create credit account
credit = Credit(
partner_id=partner.id, # type: ignore
transaction_id=transaction.id, # type: ignore
credit_amount=1000,
credit_limit=5000,
balance=1000,
created_by=user.id, # type: ignore
updated_by=user.id # type: ignore
)
integration_session.add(credit)
integration_session.commit()
integration_session.refresh(credit)
assert credit.id is not None
assert credit.partner_id == partner.id
assert credit.balance == 1000
assert credit.credit_limit == 5000
class TestComplexQueries:
"""Test complex database queries and relationships."""
def test_query_transactions_by_partner(self, integration_session: Session):
"""Test querying transactions by partner."""
# Create test data
user = User(username="query_user", password_hash="hash", role=UserRole.ADMIN)
partner = Partner(
tin_number=123456789,
names="Query Partner",
type=PartnerType.CLIENT,
phone_number="1234567890"
)
integration_session.add(user)
integration_session.add(partner)
integration_session.commit()
integration_session.refresh(user)
integration_session.refresh(partner)
# Create multiple transactions
for amount in [100, 200, 300]:
transaction = Transaction(
partner_id=partner.id, # type: ignore
transcation_type=TransactionType.SALE,
transaction_status=TransactionStatus.UNPAID,
total_amount=amount,
created_by=user.id, # type: ignore
updated_by=user.id # type: ignore
)
integration_session.add(transaction)
integration_session.commit()
# Query transactions by partner
transactions = integration_session.exec(
select(Transaction).where(Transaction.partner_id == partner.id)
).all()
assert len(transactions) == 3
amounts = [t.total_amount for t in transactions]
assert 100 in amounts
assert 200 in amounts
assert 300 in amounts
def test_database_rollback_on_error(self, integration_session: Session):
"""Test that database properly rolls back on constraint violations."""
user = User(username="rollback_user", password_hash="hash", role=UserRole.ADMIN)
integration_session.add(user)
integration_session.commit()
# Attempt to create duplicate username (should fail)
duplicate_user = User(username="rollback_user", password_hash="hash2", role=UserRole.READ_ONLY)
integration_session.add(duplicate_user)
with pytest.raises(IntegrityError):
integration_session.commit()
# Verify rollback - session should still be usable
integration_session.rollback()
new_user = User(username="new_user", password_hash="hash", role=UserRole.READ_ONLY)
integration_session.add(new_user)
integration_session.commit()
integration_session.refresh(new_user)
assert new_user.id is not None
assert new_user.username == "new_user"