test_financial.py•10.4 kB
"""Unit tests for Financial models.
Tests FinancialReport, RevenueBreakdown, ExpenseBreakdown models.
Following TDD: These tests should FAIL until models are implemented.
"""
from datetime import date
from decimal import Decimal
import pytest
from pydantic import ValidationError
# Unit tests for financial models
class TestFinancialModels:
"""Test suite for Financial-related Pydantic models."""
def test_financial_report_creation_with_all_fields(self) -> None:
"""Test that FinancialReport model can be created with all fields."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod
#
# report = FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# listing_id=12345,
# listing_name="Cozy Downtown Apartment",
# revenue=RevenueBreakdown(
# total_revenue=Decimal("12500.00"),
# direct_bookings=Decimal("3000.00"),
# airbnb=Decimal("6000.00"),
# vrbo=Decimal("2500.00"),
# booking_com=Decimal("1000.00"),
# ),
# expenses=ExpenseBreakdown(
# total_expenses=Decimal("3250.00"),
# cleaning=Decimal("1200.00"),
# platform_fees=Decimal("1000.00"),
# ),
# net_income=Decimal("9250.00"),
# total_bookings=15,
# total_nights_booked=75,
# average_daily_rate=Decimal("166.67"),
# occupancy_rate=Decimal("80.65"),
# )
#
# assert report.period_start == date(2025, 10, 1)
# assert report.period_type == FinancialReportPeriod.MONTHLY
# assert report.revenue.total_revenue == Decimal("12500.00")
# assert report.net_income == Decimal("9250.00")
pass
def test_revenue_breakdown_model(self) -> None:
"""Test RevenueBreakdown model for revenue tracking."""
# TODO: Import models once implemented
# from src.models.financial import RevenueBreakdown
#
# revenue = RevenueBreakdown(
# total_revenue=Decimal("12500.00"),
# direct_bookings=Decimal("3000.00"),
# airbnb=Decimal("6000.00"),
# vrbo=Decimal("2500.00"),
# booking_com=Decimal("1000.00"),
# )
#
# assert revenue.total_revenue == Decimal("12500.00")
# assert revenue.airbnb == Decimal("6000.00")
pass
def test_expense_breakdown_model(self) -> None:
"""Test ExpenseBreakdown model for expense tracking."""
# TODO: Import models once implemented
# from src.models.financial import ExpenseBreakdown
#
# expenses = ExpenseBreakdown(
# total_expenses=Decimal("3250.00"),
# cleaning=Decimal("1200.00"),
# maintenance=Decimal("500.00"),
# utilities=Decimal("300.00"),
# platform_fees=Decimal("1000.00"),
# supplies=Decimal("200.00"),
# other=Decimal("50.00"),
# )
#
# assert expenses.total_expenses == Decimal("3250.00")
# assert expenses.cleaning == Decimal("1200.00")
pass
def test_financial_report_period_enum(self) -> None:
"""Test FinancialReportPeriod enum values."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReportPeriod
#
# # Test enum values
# assert FinancialReportPeriod.DAILY == "daily"
# assert FinancialReportPeriod.WEEKLY == "weekly"
# assert FinancialReportPeriod.MONTHLY == "monthly"
# assert FinancialReportPeriod.QUARTERLY == "quarterly"
# assert FinancialReportPeriod.YEARLY == "yearly"
# assert FinancialReportPeriod.CUSTOM == "custom"
pass
def test_financial_report_profit_margin_property(self) -> None:
"""Test FinancialReport profit_margin property calculation."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod
#
# report = FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# revenue=RevenueBreakdown(total_revenue=Decimal("10000.00")),
# expenses=ExpenseBreakdown(total_expenses=Decimal("3000.00")),
# net_income=Decimal("7000.00"),
# total_bookings=10,
# total_nights_booked=50,
# average_daily_rate=Decimal("200.00"),
# occupancy_rate=Decimal("75.00"),
# )
#
# # Profit margin = (7000 / 10000) * 100 = 70%
# assert report.profit_margin == Decimal("70.00")
pass
def test_financial_report_profit_margin_zero_revenue(self) -> None:
"""Test profit_margin returns 0 when revenue is zero."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod
#
# report = FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# revenue=RevenueBreakdown(total_revenue=Decimal("0.00")),
# expenses=ExpenseBreakdown(total_expenses=Decimal("1000.00")),
# net_income=Decimal("-1000.00"),
# total_bookings=0,
# total_nights_booked=0,
# average_daily_rate=Decimal("0.00"),
# occupancy_rate=Decimal("0.00"),
# )
#
# assert report.profit_margin == Decimal("0.00")
pass
def test_financial_report_validation_negative_amounts(self) -> None:
"""Test that FinancialReport validates non-negative amounts."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod
#
# with pytest.raises(ValidationError) as exc_info:
# FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# revenue=RevenueBreakdown(total_revenue=Decimal("-1000.00")), # Negative!
# expenses=ExpenseBreakdown(total_expenses=Decimal("500.00")),
# net_income=Decimal("-1500.00"),
# total_bookings=0,
# total_nights_booked=0,
# average_daily_rate=Decimal("0.00"),
# occupancy_rate=Decimal("0.00"),
# )
#
# assert "total_revenue" in str(exc_info.value)
pass
def test_financial_report_occupancy_rate_validation(self) -> None:
"""Test that occupancy_rate is validated between 0-100."""
# TODO: Import models once implemented
# from src.models.financial import FinancialReport, RevenueBreakdown, ExpenseBreakdown, FinancialReportPeriod
#
# # Valid: 0-100
# report = FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# revenue=RevenueBreakdown(total_revenue=Decimal("5000.00")),
# expenses=ExpenseBreakdown(total_expenses=Decimal("1000.00")),
# net_income=Decimal("4000.00"),
# total_bookings=5,
# total_nights_booked=20,
# average_daily_rate=Decimal("250.00"),
# occupancy_rate=Decimal("95.50"),
# )
# assert report.occupancy_rate == Decimal("95.50")
#
# # Invalid: > 100
# with pytest.raises(ValidationError) as exc_info:
# FinancialReport(
# period_start=date(2025, 10, 1),
# period_end=date(2025, 10, 31),
# period_type=FinancialReportPeriod.MONTHLY,
# revenue=RevenueBreakdown(total_revenue=Decimal("5000.00")),
# expenses=ExpenseBreakdown(total_expenses=Decimal("1000.00")),
# net_income=Decimal("4000.00"),
# total_bookings=5,
# total_nights_booked=20,
# average_daily_rate=Decimal("250.00"),
# occupancy_rate=Decimal("105.00"), # > 100!
# )
# assert "occupancy_rate" in str(exc_info.value)
pass
def test_revenue_breakdown_channel_defaults(self) -> None:
"""Test RevenueBreakdown has proper defaults for channels."""
# TODO: Import models once implemented
# from src.models.financial import RevenueBreakdown
#
# revenue = RevenueBreakdown(
# total_revenue=Decimal("5000.00"),
# airbnb=Decimal("5000.00"), # Only airbnb, rest default to 0
# )
#
# assert revenue.total_revenue == Decimal("5000.00")
# assert revenue.airbnb == Decimal("5000.00")
# assert revenue.direct_bookings == Decimal("0.00")
# assert revenue.vrbo == Decimal("0.00")
# assert revenue.booking_com == Decimal("0.00")
# assert revenue.other == Decimal("0.00")
pass
def test_expense_breakdown_category_defaults(self) -> None:
"""Test ExpenseBreakdown has proper defaults for categories."""
# TODO: Import models once implemented
# from src.models.financial import ExpenseBreakdown
#
# expenses = ExpenseBreakdown(
# total_expenses=Decimal("1000.00"),
# cleaning=Decimal("1000.00"), # Only cleaning, rest default to 0
# )
#
# assert expenses.total_expenses == Decimal("1000.00")
# assert expenses.cleaning == Decimal("1000.00")
# assert expenses.maintenance == Decimal("0.00")
# assert expenses.utilities == Decimal("0.00")
# assert expenses.platform_fees == Decimal("0.00")
# assert expenses.supplies == Decimal("0.00")
# assert expenses.other == Decimal("0.00")
pass