"""Tests for backtest job execution via the jobs module.""" from __future__ import annotations from datetime import date from app.services.backtesting.jobs import ( BacktestJobStore, JobStage, JobStatus, job_store, run_backtest_job, ) from app.services.backtesting.ui_service import BacktestPageService def test_backtest_job_store_creates_and_retrieves_jobs() -> None: """Test that the job store can create and retrieve jobs.""" store = BacktestJobStore() job = store.create_job("workspace-1") assert job.status == JobStatus.PENDING assert job.stage == JobStage.VALIDATING retrieved = store.get_job("workspace-1") assert retrieved is job # Creating a new job replaces the old one job2 = store.create_job("workspace-1") assert store.get_job("workspace-1") is job2 def test_backtest_job_store_updates_job_status() -> None: """Test that job status can be updated.""" store = BacktestJobStore() store.create_job("workspace-1") store.update_job( "workspace-1", status=JobStatus.RUNNING, stage=JobStage.FETCHING_PRICES, progress=50, message="Fetching prices...", ) updated = store.get_job("workspace-1") assert updated is not None assert updated.status == JobStatus.RUNNING assert updated.stage == JobStage.FETCHING_PRICES assert updated.progress == 50 assert updated.message == "Fetching prices..." def test_run_backtest_job_completes_with_synthetic_data() -> None: """Test that a backtest job completes successfully with synthetic fixture data.""" # Use the global job_store singleton workspace_id = "workspace-test-job-complete" job = job_store.create_job(workspace_id) service = BacktestPageService() run_backtest_job( workspace_id=workspace_id, job=job, service=service, symbol="GLD", start_date=date(2024, 1, 2), end_date=date(2024, 1, 8), template_slug="protective-put-atm-12m", underlying_units=1000.0, loan_amount=68000.0, margin_call_ltv=0.75, data_source="synthetic", ) updated = job_store.get_job(workspace_id) assert updated is not None assert updated.status == JobStatus.COMPLETE, f"Job failed with error: {updated.error}" assert updated.result is not None # Verify result structure result = updated.result assert "scenario_id" in result assert "entry_spot" in result assert result["entry_spot"] == 100.0 assert "template_results" in result assert len(result["template_results"]) == 1 # Verify template result structure template_result = result["template_results"][0] assert "template_slug" in template_result assert "summary_metrics" in template_result assert "daily_path" in template_result # Verify there are daily points daily_path = template_result["daily_path"] assert len(daily_path) == 5 # 5 trading days in fixture # Verify summary metrics exist summary = template_result["summary_metrics"] assert "start_value" in summary assert "total_hedge_cost" in summary def test_run_backtest_job_serializes_daily_path_correctly() -> None: """Test that daily_path is properly serialized with all fields.""" workspace_id = "workspace-test-daily-path" job = job_store.create_job(workspace_id) service = BacktestPageService() run_backtest_job( workspace_id=workspace_id, job=job, service=service, symbol="GLD", start_date=date(2024, 1, 2), end_date=date(2024, 1, 8), template_slug="protective-put-atm-12m", underlying_units=1000.0, loan_amount=68000.0, margin_call_ltv=0.75, data_source="synthetic", ) result = job_store.get_job(workspace_id).result daily_point = result["template_results"][0]["daily_path"][0] # Verify all fields are present assert "date" in daily_point assert "spot_close" in daily_point assert "underlying_value" in daily_point assert "option_market_value" in daily_point assert "net_portfolio_value" in daily_point assert "ltv_hedged" in daily_point assert "ltv_unhedged" in daily_point assert "margin_call_hedged" in daily_point assert "margin_call_unhedged" in daily_point def test_run_backtest_job_handles_validation_errors() -> None: """Test that validation errors are properly captured.""" workspace_id = "workspace-test-validation" job = job_store.create_job(workspace_id) service = BacktestPageService() # Use invalid dates (outside fixture window for synthetic) run_backtest_job( workspace_id=workspace_id, job=job, service=service, symbol="GLD", start_date=date(2023, 1, 1), # Outside fixture window end_date=date(2023, 1, 7), template_slug="protective-put-atm-12m", underlying_units=1000.0, loan_amount=68000.0, margin_call_ltv=0.75, data_source="synthetic", ) updated = job_store.get_job(workspace_id) assert updated is not None assert updated.status == JobStatus.FAILED assert updated.error is not None assert "deterministic fixture data" in updated.error def test_run_backtest_job_handles_invalid_symbol() -> None: """Test that invalid symbols are properly rejected.""" workspace_id = "workspace-test-invalid-symbol" job = job_store.create_job(workspace_id) service = BacktestPageService() run_backtest_job( workspace_id=workspace_id, job=job, service=service, symbol="INVALID", start_date=date(2024, 1, 2), end_date=date(2024, 1, 8), template_slug="protective-put-atm-12m", underlying_units=1000.0, loan_amount=68000.0, margin_call_ltv=0.75, data_source="synthetic", ) updated = job_store.get_job(workspace_id) assert updated is not None assert updated.status == JobStatus.FAILED assert "GLD, GC, XAU" in updated.error