"""Tests for storage cost calculations.""" from __future__ import annotations from decimal import Decimal from uuid import uuid4 from app.models.position import Position, create_position from app.services.storage_costs import ( calculate_annual_storage_cost, calculate_total_storage_cost, get_default_storage_cost_for_underlying, ) class TestCalculateAnnualStorageCost: """Test calculate_annual_storage_cost function.""" def test_no_storage_cost_returns_zero(self) -> None: """Test that position without storage cost returns zero.""" pos = create_position( underlying="GLD", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=None, ) current_value = Decimal("215000") result = calculate_annual_storage_cost(pos, current_value) assert result == Decimal("0") def test_percentage_annual_cost(self) -> None: """Test percentage-based annual storage cost.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), # 0.12% storage_cost_period="annual", ) current_value = Decimal("215000") result = calculate_annual_storage_cost(pos, current_value) # 0.12% of 215000 = 258 expected = Decimal("258") assert result == expected def test_percentage_monthly_cost_annualized(self) -> None: """Test percentage-based monthly storage cost is annualized.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.01"), # 0.01% per month storage_cost_period="monthly", ) current_value = Decimal("215000") result = calculate_annual_storage_cost(pos, current_value) # 0.01% monthly × 12 = 0.12% annual # 0.12% of 215000 = 258 expected = Decimal("258") assert result == expected def test_fixed_annual_cost(self) -> None: """Test fixed annual storage cost.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("300"), # $300 per year storage_cost_period="annual", ) current_value = Decimal("215000") result = calculate_annual_storage_cost(pos, current_value) assert result == Decimal("300") def test_fixed_monthly_cost_annualized(self) -> None: """Test fixed monthly storage cost is annualized.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("25"), # $25 per month storage_cost_period="monthly", ) current_value = Decimal("215000") result = calculate_annual_storage_cost(pos, current_value) # $25 × 12 = $300 per year expected = Decimal("300") assert result == expected def test_percentage_boundary_at_one(self) -> None: """Test that basis < 1 is treated as percentage, >= 1 as fixed.""" # 0.99 should be treated as percentage pos_pct = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.99"), storage_cost_period="annual", ) current_value = Decimal("100000") result_pct = calculate_annual_storage_cost(pos_pct, current_value) # 0.99% of 100000 = 990 assert result_pct == Decimal("990") # 1.00 should be treated as fixed pos_fixed = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("1"), storage_cost_period="annual", ) result_fixed = calculate_annual_storage_cost(pos_fixed, current_value) assert result_fixed == Decimal("1") class TestCalculateTotalStorageCost: """Test calculate_total_storage_cost function.""" def test_empty_positions_returns_zero(self) -> None: """Test that empty position list returns zero.""" result = calculate_total_storage_cost([], {}) assert result == Decimal("0") def test_multiple_positions_summed(self) -> None: """Test that multiple positions have costs summed.""" pos1 = create_position( underlying="XAU", quantity=Decimal("50"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", ) pos2 = create_position( underlying="XAU", quantity=Decimal("50"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", ) positions = [pos1, pos2] current_values = { str(pos1.id): Decimal("107500"), # 50 × 2150 str(pos2.id): Decimal("107500"), } result = calculate_total_storage_cost(positions, current_values) # Each: 0.12% of 107500 = 129 # Total: 129 + 129 = 258 expected = Decimal("258") assert result == expected def test_mixed_positions_with_and_without_costs(self) -> None: """Test positions with and without storage costs.""" pos_xau = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", ) pos_gld = create_position( underlying="GLD", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=None, # GLD has no storage cost ) positions = [pos_xau, pos_gld] current_values = { str(pos_xau.id): Decimal("215000"), str(pos_gld.id): Decimal("215000"), } result = calculate_total_storage_cost(positions, current_values) # Only XAU position has cost: 0.12% of 215000 = 258 expected = Decimal("258") assert result == expected class TestGetDefaultStorageCostForUnderlying: """Test get_default_storage_cost_for_underlying function.""" def test_xau_default(self) -> None: """Test XAU gets 0.12% annual default.""" basis, period = get_default_storage_cost_for_underlying("XAU") assert basis == Decimal("0.12") assert period == "annual" def test_gld_default(self) -> None: """Test GLD gets no storage cost (expense ratio in price).""" basis, period = get_default_storage_cost_for_underlying("GLD") assert basis is None assert period is None def test_gc_f_default(self) -> None: """Test GC=F gets no storage cost (roll costs deferred).""" basis, period = get_default_storage_cost_for_underlying("GC=F") assert basis is None assert period is None def test_unknown_underlying_default(self) -> None: """Test unknown underlying gets no storage cost.""" basis, period = get_default_storage_cost_for_underlying("UNKNOWN") assert basis is None assert period is None class TestPositionStorageCostFields: """Test Position model storage cost fields.""" def test_position_with_storage_cost_fields(self) -> None: """Test Position can be created with storage cost fields.""" pos = Position( id=uuid4(), underlying="XAU", quantity=Decimal("100"), unit="oz", entry_price=Decimal("2150"), entry_date="2025-01-01", storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", storage_cost_currency="USD", ) assert pos.storage_cost_basis == Decimal("0.12") assert pos.storage_cost_period == "annual" assert pos.storage_cost_currency == "USD" def test_position_default_storage_cost_fields(self) -> None: """Test Position defaults for storage cost fields.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), ) assert pos.storage_cost_basis is None assert pos.storage_cost_period is None assert pos.storage_cost_currency == "USD" def test_position_serialization_with_storage_costs(self) -> None: """Test Position serialization includes storage cost fields.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", ) data = pos.to_dict() assert data["storage_cost_basis"] == "0.12" assert data["storage_cost_period"] == "annual" assert data["storage_cost_currency"] == "USD" def test_position_deserialization_with_storage_costs(self) -> None: """Test Position deserialization restores storage cost fields.""" pos = create_position( underlying="XAU", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=Decimal("0.12"), storage_cost_period="annual", ) data = pos.to_dict() restored = Position.from_dict(data) assert restored.storage_cost_basis == Decimal("0.12") assert restored.storage_cost_period == "annual" assert restored.storage_cost_currency == "USD" def test_position_serialization_with_null_storage_costs(self) -> None: """Test Position serialization handles null storage cost fields.""" pos = create_position( underlying="GLD", quantity=Decimal("100"), entry_price=Decimal("2150"), storage_cost_basis=None, ) data = pos.to_dict() assert data["storage_cost_basis"] is None assert data["storage_cost_period"] is None assert data["storage_cost_currency"] == "USD"