- Engine: Add ppa_revenue_cr, mcp_revenue_cr, tariff, units to PnLRow - Engine: Split PPA vs MCP revenue in P&L computation - Web: Collapsible rows for PPA/MCP Revenue and Opex - Web: Highlighted rows (Total Revenue, EBITDA, EBIT, PBT, PAT) - Web: Units above Tariff in breakdown, bg-blue-50 highlight - Fix sticky column z-index for horizontal scroll - CLAUDE.md: Add project documentation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
69 lines
2.2 KiB
Python
69 lines
2.2 KiB
Python
"""Worker task tests (S5-T10)."""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from remodel_api.db.models import Scenario
|
|
from remodel_api.workers.tasks import run_scenario_task
|
|
|
|
|
|
def _make_session_mock(scenario: Scenario | None) -> tuple[AsyncMock, MagicMock]:
|
|
session_mock = AsyncMock()
|
|
session_mock.__aenter__ = AsyncMock(return_value=session_mock)
|
|
session_mock.__aexit__ = AsyncMock(return_value=False)
|
|
session_mock.get = AsyncMock(return_value=scenario)
|
|
session_mock.commit = AsyncMock()
|
|
factory_mock = MagicMock()
|
|
factory_mock.return_value = session_mock
|
|
return session_mock, factory_mock
|
|
|
|
|
|
async def test_run_scenario_task_not_found() -> None:
|
|
mock_redis = AsyncMock()
|
|
mock_redis.publish = AsyncMock()
|
|
mock_redis.aclose = AsyncMock()
|
|
_, factory_mock = _make_session_mock(None)
|
|
|
|
with (
|
|
patch("remodel_api.workers.tasks.aioredis.from_url", return_value=mock_redis),
|
|
patch("remodel_api.workers.tasks.AsyncSessionLocal", factory_mock),
|
|
):
|
|
result = await run_scenario_task({}, "missing-id")
|
|
|
|
assert "error" in result
|
|
|
|
|
|
async def test_run_scenario_task_with_engine() -> None:
|
|
"""Worker runs the engine and persists KPIs."""
|
|
mock_redis = AsyncMock()
|
|
mock_redis.publish = AsyncMock()
|
|
mock_redis.aclose = AsyncMock()
|
|
|
|
scenario = Scenario(
|
|
id="test-id",
|
|
name="test",
|
|
status="queued",
|
|
inputs_json=(
|
|
'{"solar": {"location_id": "RJ", "capacity_dc_mwp": 10.0, "capacity_ac_mw": 8.0}}'
|
|
),
|
|
)
|
|
_, factory_mock = _make_session_mock(scenario)
|
|
|
|
engine_output = {
|
|
"status": "success",
|
|
"kpis": {"equity_irr": 0.15},
|
|
"statements": {"pnl": [], "cfs": [], "bs": []},
|
|
"debt_schedule": [],
|
|
"irr_metrics": {},
|
|
"runtime_s": 1.0,
|
|
"warnings": [],
|
|
}
|
|
|
|
with (
|
|
patch("remodel_api.workers.tasks.aioredis.from_url", return_value=mock_redis),
|
|
patch("remodel_api.workers.tasks.AsyncSessionLocal", factory_mock),
|
|
patch("remodel_api.workers.tasks._run_engine", return_value=engine_output),
|
|
):
|
|
result = await run_scenario_task({}, "test-id")
|
|
|
|
assert result.get("status") == "success"
|
|
assert mock_redis.publish.called
|