feat: overhaul UI styling, improve templates, enhance services and tests
This commit is contained in:
+67
-23
@@ -49,7 +49,9 @@ class TestAdminAuth:
|
||||
|
||||
def test_correct_token_accepted(self, auth_client, admin_headers):
|
||||
"""正确 token 应被接受(crawl 可能会失败但不是 401)。"""
|
||||
with patch("app.routes.admin.crawl_daily", new_callable=AsyncMock) as mock_crawl:
|
||||
with patch(
|
||||
"app.routes.admin.crawl_daily", new_callable=AsyncMock
|
||||
) as mock_crawl:
|
||||
mock_crawl.return_value = {"found": 0, "new": 0, "status": "success"}
|
||||
resp = auth_client.post("/admin/crawl", headers=admin_headers)
|
||||
assert resp.status_code != 401
|
||||
@@ -75,8 +77,15 @@ class TestAdminAuth:
|
||||
original = config_mod.settings.ADMIN_TOKEN
|
||||
config_mod.settings.ADMIN_TOKEN = ADMIN_TOKEN
|
||||
try:
|
||||
with patch("app.routes.admin.summarize_batch", new_callable=AsyncMock) as mock:
|
||||
mock.return_value = {"status": "success", "done": 0, "failed": 0, "total": 0}
|
||||
with patch(
|
||||
"app.routes.admin.summarize_batch", new_callable=AsyncMock
|
||||
) as mock:
|
||||
mock.return_value = {
|
||||
"status": "success",
|
||||
"done": 0,
|
||||
"failed": 0,
|
||||
"total": 0,
|
||||
}
|
||||
resp = client.post("/admin/summarize", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "success"
|
||||
@@ -114,7 +123,9 @@ class TestAdminCrawl:
|
||||
|
||||
def test_crawl_default_today(self, auth_client, admin_headers):
|
||||
"""不指定日期时默认抓取今天。"""
|
||||
with patch("app.routes.admin.crawl_daily", new_callable=AsyncMock) as mock_crawl:
|
||||
with patch(
|
||||
"app.routes.admin.crawl_daily", new_callable=AsyncMock
|
||||
) as mock_crawl:
|
||||
mock_crawl.return_value = {"found": 5, "new": 3, "status": "success"}
|
||||
resp = auth_client.post("/admin/crawl", headers=admin_headers)
|
||||
assert resp.status_code == 200
|
||||
@@ -124,9 +135,13 @@ class TestAdminCrawl:
|
||||
|
||||
def test_crawl_specific_date(self, auth_client, admin_headers):
|
||||
"""指定日期抓取。"""
|
||||
with patch("app.routes.admin.crawl_daily", new_callable=AsyncMock) as mock_crawl:
|
||||
with patch(
|
||||
"app.routes.admin.crawl_daily", new_callable=AsyncMock
|
||||
) as mock_crawl:
|
||||
mock_crawl.return_value = {"found": 2, "new": 1, "status": "success"}
|
||||
resp = auth_client.post("/admin/crawl?date=2024-01-15", headers=admin_headers)
|
||||
resp = auth_client.post(
|
||||
"/admin/crawl?date=2024-01-15", headers=admin_headers
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
mock_crawl.assert_called_once()
|
||||
call_args = mock_crawl.call_args
|
||||
@@ -157,9 +172,11 @@ class TestAdminCleanup:
|
||||
mock_cleanup.return_value = {"scanned": 0, "removed": 0, "errors": []}
|
||||
auth_client.post("/admin/cleanup", headers=admin_headers)
|
||||
|
||||
logs = db_session.execute(
|
||||
select(CrawlLog).where(CrawlLog.task == "cleanup")
|
||||
).scalars().all()
|
||||
logs = (
|
||||
db_session.execute(select(CrawlLog).where(CrawlLog.task == "cleanup"))
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
assert len(logs) >= 1
|
||||
assert logs[-1].status == "success"
|
||||
|
||||
@@ -186,7 +203,9 @@ class TestAdminDelete:
|
||||
)
|
||||
assert resp.status_code == 422
|
||||
|
||||
def test_delete_with_confirm(self, auth_client, admin_headers, db_session, sample_papers_range):
|
||||
def test_delete_with_confirm(
|
||||
self, auth_client, admin_headers, db_session, sample_papers_range
|
||||
):
|
||||
"""confirm='DELETE' 时应执行删除。"""
|
||||
resp = auth_client.post(
|
||||
"/admin/delete",
|
||||
@@ -247,13 +266,20 @@ class TestAdminLogs:
|
||||
resp = auth_client.get("/admin/logs")
|
||||
assert resp.status_code in (403, 401)
|
||||
|
||||
def test_logs_contains_data(self, auth_client, admin_headers, db_session, sample_papers_range):
|
||||
def test_logs_contains_data(
|
||||
self, auth_client, admin_headers, db_session, sample_papers_range
|
||||
):
|
||||
"""日志页面应包含日志数据。"""
|
||||
# 先创建一条日志
|
||||
now = datetime.now(timezone.utc)
|
||||
db_session.add(CrawlLog(
|
||||
task="crawl", status="success", started_at=now, completed_at=now,
|
||||
))
|
||||
db_session.add(
|
||||
CrawlLog(
|
||||
task="crawl",
|
||||
status="success",
|
||||
started_at=now,
|
||||
completed_at=now,
|
||||
)
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
resp = auth_client.get("/admin/logs", headers=admin_headers)
|
||||
@@ -273,9 +299,11 @@ class TestScheduler:
|
||||
"""SCHEDULER_ENABLED=false 时不应启动调度器。"""
|
||||
monkeypatch.setattr(settings, "SCHEDULER_ENABLED", False)
|
||||
import app.services.scheduler as sched_mod
|
||||
|
||||
sched_mod._scheduler = None
|
||||
|
||||
from app.services.scheduler import start_scheduler
|
||||
|
||||
result = start_scheduler()
|
||||
assert result is None
|
||||
|
||||
@@ -285,9 +313,11 @@ class TestScheduler:
|
||||
monkeypatch.setattr(settings, "SCHEDULER_ENABLED", True)
|
||||
monkeypatch.setattr(settings, "APP_WORKERS", 1)
|
||||
import app.services.scheduler as sched_mod
|
||||
|
||||
sched_mod._scheduler = None
|
||||
|
||||
from app.services.scheduler import start_scheduler, stop_scheduler
|
||||
|
||||
scheduler = start_scheduler()
|
||||
assert scheduler is not None
|
||||
|
||||
@@ -305,9 +335,11 @@ class TestScheduler:
|
||||
monkeypatch.setattr(settings, "SCHEDULER_ENABLED", True)
|
||||
monkeypatch.setattr(settings, "APP_WORKERS", 4)
|
||||
import app.services.scheduler as sched_mod
|
||||
|
||||
sched_mod._scheduler = None
|
||||
|
||||
from app.services.scheduler import start_scheduler, stop_scheduler
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
scheduler = start_scheduler()
|
||||
|
||||
@@ -356,15 +388,21 @@ class TestTaskLocks:
|
||||
"""同一 task + lock_key 只能有一个 running 锁。"""
|
||||
now = datetime.now(timezone.utc)
|
||||
lock1 = TaskLock(
|
||||
task="crawl", lock_key="2024-01-15",
|
||||
status="running", owner="test1", acquired_at=now,
|
||||
task="crawl",
|
||||
lock_key="2024-01-15",
|
||||
status="running",
|
||||
owner="test1",
|
||||
acquired_at=now,
|
||||
)
|
||||
db_session.add(lock1)
|
||||
db_session.commit()
|
||||
|
||||
lock2 = TaskLock(
|
||||
task="crawl", lock_key="2024-01-15",
|
||||
status="running", owner="test2", acquired_at=now,
|
||||
task="crawl",
|
||||
lock_key="2024-01-15",
|
||||
status="running",
|
||||
owner="test2",
|
||||
acquired_at=now,
|
||||
)
|
||||
db_session.add(lock2)
|
||||
with pytest.raises(Exception):
|
||||
@@ -375,16 +413,22 @@ class TestTaskLocks:
|
||||
"""已释放的锁允许新的 running 锁。"""
|
||||
now = datetime.now(timezone.utc)
|
||||
lock1 = TaskLock(
|
||||
task="crawl", lock_key="2024-01-16",
|
||||
status="finished", owner="test1",
|
||||
acquired_at=now, released_at=now,
|
||||
task="crawl",
|
||||
lock_key="2024-01-16",
|
||||
status="finished",
|
||||
owner="test1",
|
||||
acquired_at=now,
|
||||
released_at=now,
|
||||
)
|
||||
db_session.add(lock1)
|
||||
db_session.commit()
|
||||
|
||||
lock2 = TaskLock(
|
||||
task="crawl", lock_key="2024-01-16",
|
||||
status="running", owner="test2", acquired_at=now,
|
||||
task="crawl",
|
||||
lock_key="2024-01-16",
|
||||
status="running",
|
||||
owner="test2",
|
||||
acquired_at=now,
|
||||
)
|
||||
db_session.add(lock2)
|
||||
db_session.commit() # 应成功
|
||||
|
||||
Reference in New Issue
Block a user