TradingAgents/tests/portfolio/conftest.py

158 lines
4.9 KiB
Python

"""Shared pytest fixtures for portfolio tests.
Fixtures provided:
- ``tmp_reports`` -- temporary directory used as ReportStore base_dir
- ``sample_portfolio`` -- a Portfolio instance for testing (not persisted)
- ``sample_holding`` -- a Holding instance for testing (not persisted)
- ``sample_trade`` -- a Trade instance for testing (not persisted)
- ``sample_snapshot`` -- a PortfolioSnapshot instance for testing
- ``report_store`` -- a ReportStore instance backed by tmp_reports
- ``mock_supabase_client`` -- MagicMock of SupabaseClient for unit tests
Supabase integration tests use ``pytest.mark.skipif`` to auto-skip when
``SUPABASE_CONNECTION_STRING`` is not set in the environment.
"""
from __future__ import annotations
import os
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from tradingagents.portfolio.models import (
Holding,
Portfolio,
PortfolioSnapshot,
Trade,
)
from tradingagents.portfolio.report_store import ReportStore
from tradingagents.portfolio.supabase_client import SupabaseClient
# ---------------------------------------------------------------------------
# Skip marker for Supabase integration tests
# ---------------------------------------------------------------------------
requires_supabase = pytest.mark.skipif(
not os.getenv("SUPABASE_CONNECTION_STRING"),
reason="SUPABASE_CONNECTION_STRING not set -- skipping integration tests",
)
# ---------------------------------------------------------------------------
# Data fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def sample_portfolio_id() -> str:
"""Return a fixed UUID for deterministic testing."""
return "11111111-1111-1111-1111-111111111111"
@pytest.fixture
def sample_holding_id() -> str:
"""Return a fixed UUID for deterministic testing."""
return "22222222-2222-2222-2222-222222222222"
@pytest.fixture
def sample_portfolio(sample_portfolio_id: str) -> Portfolio:
"""Return an unsaved Portfolio instance for testing."""
return Portfolio(
portfolio_id=sample_portfolio_id,
name="Test Portfolio",
cash=50_000.0,
initial_cash=100_000.0,
currency="USD",
created_at="2026-03-20T00:00:00Z",
updated_at="2026-03-20T00:00:00Z",
report_path="reports/daily/2026-03-20/portfolio",
metadata={"strategy": "test"},
)
@pytest.fixture
def sample_holding(sample_portfolio_id: str, sample_holding_id: str) -> Holding:
"""Return an unsaved Holding instance for testing."""
return Holding(
holding_id=sample_holding_id,
portfolio_id=sample_portfolio_id,
ticker="AAPL",
shares=100.0,
avg_cost=150.0,
sector="Technology",
industry="Consumer Electronics",
created_at="2026-03-20T00:00:00Z",
updated_at="2026-03-20T00:00:00Z",
)
@pytest.fixture
def sample_trade(sample_portfolio_id: str) -> Trade:
"""Return an unsaved Trade instance for testing."""
return Trade(
trade_id="33333333-3333-3333-3333-333333333333",
portfolio_id=sample_portfolio_id,
ticker="AAPL",
action="BUY",
shares=100.0,
price=150.0,
total_value=15_000.0,
trade_date="2026-03-20T10:00:00Z",
rationale="Strong momentum signal",
signal_source="scanner",
metadata={"confidence": 0.85},
)
@pytest.fixture
def sample_snapshot(sample_portfolio_id: str) -> PortfolioSnapshot:
"""Return an unsaved PortfolioSnapshot instance for testing."""
return PortfolioSnapshot(
snapshot_id="44444444-4444-4444-4444-444444444444",
portfolio_id=sample_portfolio_id,
snapshot_date="2026-03-20",
total_value=115_000.0,
cash=50_000.0,
equity_value=65_000.0,
num_positions=2,
holdings_snapshot=[
{"ticker": "AAPL", "shares": 100.0, "avg_cost": 150.0},
{"ticker": "MSFT", "shares": 50.0, "avg_cost": 300.0},
],
metadata={"note": "end of day snapshot"},
)
# ---------------------------------------------------------------------------
# Filesystem fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def tmp_reports(tmp_path: Path) -> Path:
"""Temporary reports directory, cleaned up after each test."""
reports_dir = tmp_path / "reports"
reports_dir.mkdir()
return reports_dir
@pytest.fixture
def report_store(tmp_reports: Path) -> ReportStore:
"""ReportStore instance backed by a temporary directory."""
return ReportStore(base_dir=tmp_reports)
# ---------------------------------------------------------------------------
# Mock Supabase client fixture
# ---------------------------------------------------------------------------
@pytest.fixture
def mock_supabase_client() -> MagicMock:
"""MagicMock of SupabaseClient for unit tests that don't hit the DB."""
return MagicMock(spec=SupabaseClient)