feat: overhaul UI styling, improve templates, enhance services and tests
This commit is contained in:
+62
-32
@@ -51,7 +51,9 @@ class TestDbUpdate:
|
||||
assert summary.motivation_problem == schema.motivation.problem
|
||||
assert json.loads(summary.full_json)["title_zh"] == schema.title_zh
|
||||
|
||||
def test_paper_title_zh_updated(self, db_session, sample_paper, sample_summary_dict):
|
||||
def test_paper_title_zh_updated(
|
||||
self, db_session, sample_paper, sample_summary_dict
|
||||
):
|
||||
schema = SummarySchema.model_validate(sample_summary_dict)
|
||||
_update_summary_in_db(db_session, sample_paper, schema, "normal", "raw")
|
||||
|
||||
@@ -85,7 +87,9 @@ class TestDbUpdate:
|
||||
assert "自然语言处理" in tag_names
|
||||
assert "大语言模型" in tag_names
|
||||
|
||||
def test_existing_tags_not_duplicated(self, db_session, sample_paper, sample_summary_dict):
|
||||
def test_existing_tags_not_duplicated(
|
||||
self, db_session, sample_paper, sample_summary_dict
|
||||
):
|
||||
"""已存在的标签名(同 name)不会被 AI source 重复插入。"""
|
||||
# sample_paper 已有 NLP (hf)、LLM (hf)
|
||||
# 让 AI 输出包含 NLP(与 HF 重复)和 "新标签"(新的)
|
||||
@@ -157,7 +161,10 @@ class TestSummarizeOneFlow:
|
||||
def _patch_paths(self, tmp_path):
|
||||
"""将 data 目录重定向到 tmp_path。"""
|
||||
with (
|
||||
patch("app.services.summarizer.paper_dir", lambda aid: tmp_path / "papers" / aid),
|
||||
patch(
|
||||
"app.services.summarizer.paper_dir",
|
||||
lambda aid: tmp_path / "papers" / aid,
|
||||
),
|
||||
patch("app.services.pdf_downloader.PAPERS_DIR", tmp_path / "papers"),
|
||||
patch("app.services.pdf_downloader.TMP_DIR", tmp_path / "tmp"),
|
||||
patch("app.utils.PAPERS_DIR", tmp_path / "papers"),
|
||||
@@ -172,7 +179,11 @@ class TestSummarizeOneFlow:
|
||||
"""pending → processing → done 全流程。"""
|
||||
with (
|
||||
patch("app.services.summarizer.download_pdf", new_callable=AsyncMock),
|
||||
patch("app.services.summarizer.call_pi", new_callable=AsyncMock, return_value=mock_pi_output),
|
||||
patch(
|
||||
"app.services.summarizer.call_pi",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_pi_output,
|
||||
),
|
||||
):
|
||||
result = await summarize_one(db_session, sample_paper)
|
||||
|
||||
@@ -198,9 +209,7 @@ class TestSummarizeOneFlow:
|
||||
assert fts_row[0] == "测试论文中文标题"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pdf_download_failure(
|
||||
self, db_session, sample_paper, _patch_paths
|
||||
):
|
||||
async def test_pdf_download_failure(self, db_session, sample_paper, _patch_paths):
|
||||
"""PDF 下载失败 → error_type=pdf_download_failed,tmp 被清理。"""
|
||||
with (
|
||||
patch(
|
||||
@@ -256,13 +265,16 @@ class TestSummarizeOneFlow:
|
||||
self, db_session, sample_paper, _patch_paths
|
||||
):
|
||||
"""必填字段缺失 → field_missing → retry → permanent_failure。"""
|
||||
bad_json = json.dumps({
|
||||
"title_zh": "", # 空的必填字段
|
||||
"one_line": "valid line",
|
||||
"tags": ["tag1"],
|
||||
"motivation": {"problem": "valid problem"},
|
||||
"method": {"key_idea": "valid idea"},
|
||||
}, ensure_ascii=False)
|
||||
bad_json = json.dumps(
|
||||
{
|
||||
"title_zh": "", # 空的必填字段
|
||||
"one_line": "valid line",
|
||||
"tags": ["tag1"],
|
||||
"motivation": {"problem": "valid problem"},
|
||||
"method": {"key_idea": "valid idea"},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
bad_output = f"```json\n{bad_json}\n```"
|
||||
|
||||
with (
|
||||
@@ -314,7 +326,11 @@ class TestSummarizeOneFlow:
|
||||
"""成功后清理 tmp 目录。"""
|
||||
with (
|
||||
patch("app.services.summarizer.download_pdf", new_callable=AsyncMock),
|
||||
patch("app.services.summarizer.call_pi", new_callable=AsyncMock, return_value=mock_pi_output),
|
||||
patch(
|
||||
"app.services.summarizer.call_pi",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_pi_output,
|
||||
),
|
||||
):
|
||||
await summarize_one(db_session, sample_paper)
|
||||
|
||||
@@ -359,7 +375,10 @@ class TestBatchSummarize:
|
||||
@pytest.fixture
|
||||
def _patch_paths(self, tmp_path):
|
||||
with (
|
||||
patch("app.services.summarizer.paper_dir", lambda aid: tmp_path / "papers" / aid),
|
||||
patch(
|
||||
"app.services.summarizer.paper_dir",
|
||||
lambda aid: tmp_path / "papers" / aid,
|
||||
),
|
||||
patch("app.services.pdf_downloader.PAPERS_DIR", tmp_path / "papers"),
|
||||
patch("app.services.pdf_downloader.TMP_DIR", tmp_path / "tmp"),
|
||||
patch("app.utils.PAPERS_DIR", tmp_path / "papers"),
|
||||
@@ -390,15 +409,18 @@ class TestBatchSummarize:
|
||||
|
||||
# 每个 worker 用独立 session(同一个内存引擎)
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
_TestSession = _sm(bind=db_engine, autoflush=False, autocommit=False)
|
||||
|
||||
with (
|
||||
patch("app.services.summarizer.download_pdf", new_callable=AsyncMock),
|
||||
patch("app.services.summarizer.call_pi", new_callable=AsyncMock, return_value=mock_pi_output),
|
||||
patch(
|
||||
"app.services.summarizer.call_pi",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_pi_output,
|
||||
),
|
||||
):
|
||||
result = await summarize_batch(
|
||||
db_session, _session_factory=_TestSession
|
||||
)
|
||||
result = await summarize_batch(db_session, _session_factory=_TestSession)
|
||||
|
||||
assert result["status"] == "success"
|
||||
assert result["done"] == 3
|
||||
@@ -432,6 +454,7 @@ class TestBatchSummarize:
|
||||
db_session.commit()
|
||||
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
_TestSession = _sm(bind=db_engine, autoflush=False, autocommit=False)
|
||||
|
||||
call_count = 0
|
||||
@@ -447,9 +470,7 @@ class TestBatchSummarize:
|
||||
patch("app.services.summarizer.download_pdf", new_callable=AsyncMock),
|
||||
patch("app.services.summarizer.call_pi", side_effect=_mock_call_pi),
|
||||
):
|
||||
result = await summarize_batch(
|
||||
db_session, _session_factory=_TestSession
|
||||
)
|
||||
result = await summarize_batch(db_session, _session_factory=_TestSession)
|
||||
|
||||
assert result["done"] == 1
|
||||
assert result["failed"] == 1
|
||||
@@ -472,23 +493,32 @@ class TestBatchSummarize:
|
||||
assert result["status"] == "conflict"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_task_lock_released(self, db_session, db_engine, mock_pi_output, _patch_paths):
|
||||
async def test_task_lock_released(
|
||||
self, db_session, db_engine, mock_pi_output, _patch_paths
|
||||
):
|
||||
"""完成后释放 TaskLock。"""
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
_TestSession = _sm(bind=db_engine, autoflush=False, autocommit=False)
|
||||
|
||||
with (
|
||||
patch("app.services.summarizer.download_pdf", new_callable=AsyncMock),
|
||||
patch("app.services.summarizer.call_pi", new_callable=AsyncMock, return_value=mock_pi_output),
|
||||
patch(
|
||||
"app.services.summarizer.call_pi",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_pi_output,
|
||||
),
|
||||
):
|
||||
await summarize_batch(
|
||||
db_session, _session_factory=_TestSession
|
||||
)
|
||||
await summarize_batch(db_session, _session_factory=_TestSession)
|
||||
|
||||
locks = db_session.query(TaskLock).filter(
|
||||
TaskLock.task == "summarize",
|
||||
TaskLock.lock_key == "batch",
|
||||
).all()
|
||||
locks = (
|
||||
db_session.query(TaskLock)
|
||||
.filter(
|
||||
TaskLock.task == "summarize",
|
||||
TaskLock.lock_key == "batch",
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for lock in locks:
|
||||
assert lock.status == "finished"
|
||||
assert lock.released_at is not None
|
||||
|
||||
Reference in New Issue
Block a user