"""
测试数据模型 - Transaction, Account, Category 等核心数据结构
这些测试验证:
1. 数据模型的创建和验证
2. 业务规则的正确实现
3. 错误处理和边界情况
"""
import pytest
from datetime import datetime, date
from decimal import Decimal
from typing import Dict, Any
# 这些导入会失败,因为我们还没有实现模型
from accounting_mcp.models import Transaction, Account, Category, TransactionType
class TestTransaction:
"""测试交易记录模型"""
def test_create_valid_expense_transaction(self) -> None:
"""测试创建有效的支出交易"""
transaction = Transaction(
amount=Decimal("-50.00"),
category="food",
description="午餐",
date=date(2025, 1, 15)
)
assert transaction.amount == Decimal("-50.00")
assert transaction.category == "food"
assert transaction.description == "午餐"
assert transaction.date == date(2025, 1, 15)
assert transaction.transaction_type == TransactionType.EXPENSE
assert transaction.id is not None
assert isinstance(transaction.timestamp, datetime)
def test_create_valid_income_transaction(self) -> None:
"""测试创建有效的收入交易"""
transaction = Transaction(
amount=Decimal("1500.00"),
category="income",
description="工资"
)
assert transaction.amount == Decimal("1500.00")
assert transaction.transaction_type == TransactionType.INCOME
assert transaction.category == "income"
def test_transaction_amount_precision(self) -> None:
"""测试金额精度验证(最多2位小数)"""
# 有效精度
transaction = Transaction(
amount=Decimal("123.45"),
category="food"
)
assert transaction.amount == Decimal("123.45")
# 超过精度应该被舍入或报错
with pytest.raises(ValueError):
Transaction(
amount=Decimal("123.456"),
category="food"
)
def test_invalid_amount_zero(self) -> None:
"""测试零金额(应该被拒绝)"""
with pytest.raises(ValueError, match="金额不能为零"):
Transaction(
amount=Decimal("0.00"),
category="food"
)
def test_invalid_category_empty(self) -> None:
"""测试空分类(应该被拒绝)"""
with pytest.raises(ValueError, match="分类不能为空"):
Transaction(
amount=Decimal("-50.00"),
category=""
)
def test_future_date_validation(self) -> None:
"""测试未来日期验证"""
from datetime import date, timedelta
future_date = date.today() + timedelta(days=1)
with pytest.raises(ValueError, match="交易日期不能是未来"):
Transaction(
amount=Decimal("-50.00"),
category="food",
date=future_date
)
def test_transaction_id_uniqueness(self) -> None:
"""测试交易ID的唯一性"""
transaction1 = Transaction(
amount=Decimal("-50.00"),
category="food"
)
transaction2 = Transaction(
amount=Decimal("-30.00"),
category="transport"
)
assert transaction1.id != transaction2.id
def test_transaction_to_dict(self) -> None:
"""测试转换为字典格式"""
transaction = Transaction(
amount=Decimal("-50.00"),
category="food",
description="午餐",
date=date(2025, 1, 15)
)
result = transaction.to_dict()
assert isinstance(result, dict)
assert result["amount"] == -50.00
assert result["category"] == "food"
assert result["description"] == "午餐"
assert result["date"] == "2025-01-15"
assert "id" in result
assert "timestamp" in result
def test_transaction_from_dict(self) -> None:
"""测试从字典创建交易"""
data = {
"id": "txn_20250115_001",
"amount": -50.00,
"category": "food",
"description": "午餐",
"date": "2025-01-15",
"timestamp": "2025-01-15T12:30:00Z"
}
transaction = Transaction.from_dict(data)
assert transaction.id == "txn_20250115_001"
assert transaction.amount == Decimal("-50.00")
assert transaction.category == "food"
assert transaction.description == "午餐"
assert transaction.date == date(2025, 1, 15)
class TestAccount:
"""测试账户模型"""
def test_create_new_account(self) -> None:
"""测试创建新账户"""
account = Account()
assert account.balance == Decimal("0.00")
assert account.total_transactions == 0
assert isinstance(account.created_at, datetime)
assert account.last_updated is not None
def test_account_add_transaction(self) -> None:
"""测试向账户添加交易"""
account = Account()
transaction = Transaction(
amount=Decimal("-50.00"),
category="food"
)
account.add_transaction(transaction)
assert account.balance == Decimal("-50.00")
assert account.total_transactions == 1
assert account.last_updated is not None
def test_account_multiple_transactions(self) -> None:
"""测试多笔交易的余额计算"""
account = Account()
# 添加收入
income = Transaction(
amount=Decimal("1000.00"),
category="income"
)
account.add_transaction(income)
# 添加支出
expense = Transaction(
amount=Decimal("-200.00"),
category="food"
)
account.add_transaction(expense)
assert account.balance == Decimal("800.00")
assert account.total_transactions == 2
def test_account_to_dict(self) -> None:
"""测试账户序列化"""
account = Account()
account.add_transaction(Transaction(
amount=Decimal("-50.00"),
category="food"
))
result = account.to_dict()
assert result["balance"] == -50.00
assert result["total_transactions"] == 1
assert "created_at" in result
assert "last_updated" in result
class TestCategory:
"""测试分类模型"""
def test_create_expense_category(self) -> None:
"""测试创建支出分类"""
category = Category(
id="food",
name="餐饮",
description="餐费、外卖等",
category_type=TransactionType.EXPENSE,
color="#FF6B6B",
icon="🍽️"
)
assert category.id == "food"
assert category.name == "餐饮"
assert category.category_type == TransactionType.EXPENSE
assert category.color == "#FF6B6B"
assert category.icon == "🍽️"
def test_create_income_category(self) -> None:
"""测试创建收入分类"""
category = Category(
id="income",
name="收入",
category_type=TransactionType.INCOME
)
assert category.category_type == TransactionType.INCOME
def test_invalid_category_id(self) -> None:
"""测试无效分类ID"""
with pytest.raises(ValueError, match="分类ID不能为空"):
Category(
id="",
name="测试"
)
def test_category_to_dict(self) -> None:
"""测试分类序列化"""
category = Category(
id="food",
name="餐饮",
category_type=TransactionType.EXPENSE
)
result = category.to_dict()
assert result["id"] == "food"
assert result["name"] == "餐饮"
assert result["type"] == "expense"
# 测试枚举类型
class TestTransactionType:
"""测试交易类型枚举"""
def test_transaction_type_values(self) -> None:
"""测试枚举值"""
assert TransactionType.INCOME.value == "income"
assert TransactionType.EXPENSE.value == "expense"
def test_transaction_type_from_amount(self) -> None:
"""测试根据金额判断交易类型"""
assert TransactionType.from_amount(Decimal("100.00")) == TransactionType.INCOME
assert TransactionType.from_amount(Decimal("-50.00")) == TransactionType.EXPENSE
with pytest.raises(ValueError):
TransactionType.from_amount(Decimal("0.00"))