"""页面路由测试 — detail、trends、compare、graceful degradation。""" from __future__ import annotations from datetime import date from unittest.mock import patch as upatch import pytest from app.config import settings # ═══════════════════════════════════════════════════════════════════════ # Detail 页 & 相似论文 # ═══════════════════════════════════════════════════════════════════════ class TestDetailPage: """论文详情页测试。""" def test_detail_page_renders(self, client, sample_papers_with_summary): """详情页正常渲染。""" resp = client.get("/paper/2401.20001") assert resp.status_code == 200 assert "测试论文" in resp.text or "Test Paper" in resp.text def test_detail_page_not_found(self, client): """不存在的论文返回 404。""" resp = client.get("/paper/nonexistent.99999") assert resp.status_code == 404 # ═══════════════════════════════════════════════════════════════════════ # Similar API(详情页内联) # ═══════════════════════════════════════════════════════════════════════ class TestDetailSimilarPapers: """详情页相似论文模块测试(CHROMA 关闭时的降级行为)。""" def test_detail_page_renders_with_similar(self, client, sample_papers_with_summary): """详情页正常渲染(含相似论文模块)。""" resp = client.get("/paper/2401.20001") assert resp.status_code == 200 assert "测试论文" in resp.text or "Test Paper" in resp.text def test_detail_page_not_found_similar(self, client): """不存在的论文返回 404。""" resp = client.get("/paper/nonexistent.99999") assert resp.status_code == 404 # ═══════════════════════════════════════════════════════════════════════ # Trends Dashboard # ═══════════════════════════════════════════════════════════════════════ class TestTrendsDashboard: """趋势看板测试。""" def test_trends_page_renders(self, client, sample_papers_with_summary): """趋势看板页面正常渲染。""" resp = client.get("/trends") assert resp.status_code == 200 assert "趋势看板" in resp.text assert "chart" in resp.text.lower() or "Chart" in resp.text def test_trends_api_returns_data(self, client, sample_papers_with_summary): """趋势 API 返回正确数据结构。""" resp = client.get("/api/stats/trends") assert resp.status_code == 200 data = resp.json() assert "daily_counts" in data assert "top_tags" in data assert "upvotes_dist" in data assert "summary_completion" in data assert isinstance(data["daily_counts"], list) assert isinstance(data["top_tags"], list) assert isinstance(data["upvotes_dist"], list) assert isinstance(data["summary_completion"], list) def test_trends_api_daily_counts(self, client, sample_papers_with_summary): """每日论文数量数据正确。""" # 使用测试数据的日期范围 with upatch("app.services.trends.date") as mock_date: mock_date.today.return_value = date(2024, 1, 20) mock_date.side_effect = lambda *a, **kw: date(*a, **kw) resp = client.get("/api/stats/trends") data = resp.json() assert len(data["daily_counts"]) == 5 for item in data["daily_counts"]: assert "date" in item assert "count" in item assert item["count"] == 1 def test_trends_api_top_tags(self, client, sample_papers_with_summary): """热门标签数据正确。""" resp = client.get("/api/stats/trends") data = resp.json() tags = {t["tag"]: t["count"] for t in data["top_tags"]} assert "NLP" in tags assert tags["NLP"] == 5 # 所有论文都有 NLP def test_trends_api_summary_completion(self, client, sample_papers_with_summary): """总结完成率数据正确。""" resp = client.get("/api/stats/trends") data = resp.json() statuses = {s["status"]: s["count"] for s in data["summary_completion"]} assert "done" in statuses assert statuses["done"] == 4 # 4 篇已完成 def test_trends_empty_db(self, client): """无数据时不崩溃。""" resp = client.get("/api/stats/trends") assert resp.status_code == 200 data = resp.json() assert data["daily_counts"] == [] assert data["top_tags"] == [] # ═══════════════════════════════════════════════════════════════════════ # Compare Page # ═══════════════════════════════════════════════════════════════════════ class TestComparePage: """论文对比页测试。""" def test_compare_page_no_ids(self, client): """无 ID 时显示输入表单。""" resp = client.get("/compare") assert resp.status_code == 200 assert "对比" in resp.text def test_compare_page_with_ids(self, client, sample_papers_with_summary): """对比多篇论文正常渲染。""" resp = client.get("/compare?ids=2401.20001,2401.20002") assert resp.status_code == 200 assert "2401.20001" in resp.text assert "2401.20002" in resp.text # 应包含对比字段 assert "一句话摘要" in resp.text assert "研究问题" in resp.text def test_compare_page_max_5(self, client, sample_papers_with_summary): """最多 5 篇。""" ids = "2401.20001,2401.20002,2401.20003,2401.20004,2401.20005" resp = client.get(f"/compare?ids={ids}") assert resp.status_code == 200 def test_compare_page_over_5_truncates(self, client, sample_papers_with_summary): """超过 5 篇截断。""" ids = "2401.20001,2401.20002,2401.20003,2401.20004,2401.20005,2401.20006" resp = client.get(f"/compare?ids={ids}") assert resp.status_code == 200 def test_compare_page_invalid_ids(self, client): """无效 ID 时显示空结果。""" resp = client.get("/compare?ids=nonexistent.99999") assert resp.status_code == 200 def test_compare_page_shows_no_summary_placeholder(self, client, sample_papers_with_summary): """无总结的论文显示占位文本。""" # 2401.20005 没有 summary(status=pending) resp = client.get("/compare?ids=2401.20005") assert resp.status_code == 200 assert "暂无总结" in resp.text # ═══════════════════════════════════════════════════════════════════════ # Nav Bar # ═══════════════════════════════════════════════════════════════════════ class TestNavBar: """导航栏测试。""" def test_nav_includes_trends_link(self, client): """导航栏应包含趋势链接。""" resp = client.get("/search") assert resp.status_code == 200 assert "/trends" in resp.text def test_nav_includes_compare_implicitly(self, client): """compare 页面可访问。""" resp = client.get("/compare") assert resp.status_code == 200 # ═══════════════════════════════════════════════════════════════════════ # Graceful Degradation(CHROMA_ENABLED=false) # ═══════════════════════════════════════════════════════════════════════ class TestGracefulDegradation: """CHROMA_ENABLED=false 时优雅降级测试。""" def test_search_works_without_chroma(self, client, monkeypatch, sample_papers_with_summary): """CHROMA 关闭时 FTS5 搜索正常工作。""" monkeypatch.setattr(settings, "CHROMA_ENABLED", False) resp = client.get("/search?q=Test") assert resp.status_code == 200 assert "Test Paper" in resp.text or "测试论文" in resp.text def test_detail_works_without_chroma(self, client, monkeypatch, sample_papers_with_summary): """CHROMA 关闭时详情页正常工作。""" monkeypatch.setattr(settings, "CHROMA_ENABLED", False) resp = client.get("/paper/2401.20001") assert resp.status_code == 200 def test_trends_works_without_chroma(self, client, monkeypatch, sample_papers_with_summary): """CHROMA 关闭时趋势看板正常工作。""" monkeypatch.setattr(settings, "CHROMA_ENABLED", False) resp = client.get("/trends") assert resp.status_code == 200 def test_compare_works_without_chroma(self, client, monkeypatch, sample_papers_with_summary): """CHROMA 关闭时对比页正常工作。""" monkeypatch.setattr(settings, "CHROMA_ENABLED", False) resp = client.get("/compare?ids=2401.20001,2401.20002") assert resp.status_code == 200