Files
daily_stock_analysis/tests/test_report_renderer.py
mumu ef328e906b feat: add configurable report language (#764)
* feat: add configurable report language

* Fix review follow-ups for history and fallback localization

* fix: prefer .env report language at startup

* fix: address report language review feedback

* fix: address latest report language review feedback
2026-03-19 22:55:17 +08:00

143 lines
4.5 KiB
Python

# -*- coding: utf-8 -*-
"""
===================================
Report Engine - Report renderer tests
===================================
Tests for Jinja2 report rendering and fallback behavior.
"""
import sys
import unittest
from unittest.mock import MagicMock
try:
import litellm # noqa: F401
except ModuleNotFoundError:
sys.modules["litellm"] = MagicMock()
from src.analyzer import AnalysisResult
from src.services.report_renderer import render
def _make_result(
code: str = "600519",
name: str = "贵州茅台",
sentiment_score: int = 72,
operation_advice: str = "持有",
analysis_summary: str = "稳健",
decision_type: str = "hold",
dashboard: dict = None,
report_language: str = "zh",
) -> AnalysisResult:
if dashboard is None:
dashboard = {
"core_conclusion": {"one_sentence": "持有观望"},
"intelligence": {"risk_alerts": []},
"battle_plan": {"sniper_points": {"stop_loss": "110"}},
}
return AnalysisResult(
code=code,
name=name,
trend_prediction="看多",
sentiment_score=sentiment_score,
operation_advice=operation_advice,
analysis_summary=analysis_summary,
decision_type=decision_type,
dashboard=dashboard,
report_language=report_language,
)
class TestReportRenderer(unittest.TestCase):
"""Report renderer tests."""
def test_render_markdown_summary_only(self) -> None:
"""Markdown platform renders with summary_only."""
r = _make_result()
out = render("markdown", [r], summary_only=True)
self.assertIsNotNone(out)
self.assertIn("决策仪表盘", out)
self.assertIn("贵州茅台", out)
self.assertIn("持有", out)
def test_render_markdown_full(self) -> None:
"""Markdown platform renders full report."""
r = _make_result()
out = render("markdown", [r], summary_only=False)
self.assertIsNotNone(out)
self.assertIn("核心结论", out)
self.assertIn("作战计划", out)
def test_render_wechat(self) -> None:
"""Wechat platform renders."""
r = _make_result()
out = render("wechat", [r])
self.assertIsNotNone(out)
self.assertIn("贵州茅台", out)
def test_render_brief(self) -> None:
"""Brief platform renders 3-5 sentence summary."""
r = _make_result()
out = render("brief", [r])
self.assertIsNotNone(out)
self.assertIn("决策简报", out)
self.assertIn("贵州茅台", out)
def test_render_markdown_in_english(self) -> None:
"""Markdown renderer switches headings and summary labels for English reports."""
r = _make_result(
name="Kweichow Moutai",
operation_advice="Buy",
analysis_summary="Momentum remains constructive.",
report_language="en",
)
out = render("markdown", [r], summary_only=True)
self.assertIsNotNone(out)
self.assertIn("Decision Dashboard", out)
self.assertIn("Summary", out)
self.assertIn("Buy", out)
def test_render_markdown_market_snapshot_uses_template_context(self) -> None:
"""Market snapshot macro should render localized labels with template context."""
r = _make_result(
code="AAPL",
name="Apple",
operation_advice="Buy",
report_language="en",
)
r.market_snapshot = {
"close": "180.10",
"prev_close": "178.25",
"open": "179.00",
"high": "181.20",
"low": "177.80",
"pct_chg": "+1.04%",
"change_amount": "1.85",
"amplitude": "1.91%",
"volume": "1200000",
"amount": "215000000",
"price": "180.35",
"volume_ratio": "1.2",
"turnover_rate": "0.8%",
"source": "polygon",
}
out = render("markdown", [r], summary_only=False)
self.assertIsNotNone(out)
self.assertIn("Market Snapshot", out)
self.assertIn("Volume Ratio", out)
def test_render_unknown_platform_returns_none(self) -> None:
"""Unknown platform returns None (caller fallback)."""
r = _make_result()
out = render("unknown_platform", [r])
self.assertIsNone(out)
def test_render_empty_results_returns_content(self) -> None:
"""Empty results still produces header."""
out = render("markdown", [], summary_only=True)
self.assertIsNotNone(out)
self.assertIn("0", out)