"""用户数据服务 + 路由测试 — 收藏、阅读状态、笔记。""" from __future__ import annotations import pytest from app.exceptions import NotFoundError, ValidationError from app.services.user_data import ( get_note, save_note, set_reading_status, toggle_bookmark, ) # ═══════════════════════════════════════════════════════════════════════ # 收藏服务测试 # ═══════════════════════════════════════════════════════════════════════ class TestBookmarkService: def test_toggle_bookmark_add(self, db_session, sample_paper): result = toggle_bookmark(db_session, "2401.12345") assert result["bookmarked"] is True assert result["arxiv_id"] == "2401.12345" def test_toggle_bookmark_remove(self, db_session, sample_paper): toggle_bookmark(db_session, "2401.12345") result = toggle_bookmark(db_session, "2401.12345") assert result["bookmarked"] is False def test_toggle_bookmark_not_found(self, db_session): with pytest.raises(NotFoundError): toggle_bookmark(db_session, "nonexistent") # ═══════════════════════════════════════════════════════════════════════ # 阅读状态服务测试 # ═══════════════════════════════════════════════════════════════════════ class TestReadingStatusService: def test_set_reading_status(self, db_session, sample_paper): result = set_reading_status(db_session, "2401.12345", "read_summary") assert result["status"] == "read_summary" assert result["arxiv_id"] == "2401.12345" def test_set_reading_status_invalid(self, db_session, sample_paper): with pytest.raises(ValidationError): set_reading_status(db_session, "2401.12345", "invalid_status") def test_update_existing_status(self, db_session, sample_paper): set_reading_status(db_session, "2401.12345", "skimmed") result = set_reading_status(db_session, "2401.12345", "read_full") assert result["status"] == "read_full" def test_set_reading_status_not_found(self, db_session): with pytest.raises(NotFoundError): set_reading_status(db_session, "nonexistent", "unread") def test_all_valid_statuses(self, db_session, sample_paper): for status in ("unread", "skimmed", "read_summary", "read_full"): result = set_reading_status(db_session, "2401.12345", status) assert result["status"] == status # ═══════════════════════════════════════════════════════════════════════ # 笔记服务测试 # ═══════════════════════════════════════════════════════════════════════ class TestNoteService: def test_save_and_get_note(self, db_session, sample_paper): save_note(db_session, "2401.12345", "这是一条测试笔记") result = get_note(db_session, "2401.12345") assert result["content"] == "这是一条测试笔记" assert result["arxiv_id"] == "2401.12345" assert result["updated_at"] is not None def test_update_note(self, db_session, sample_paper): save_note(db_session, "2401.12345", "旧笔记") save_note(db_session, "2401.12345", "新笔记") result = get_note(db_session, "2401.12345") assert result["content"] == "新笔记" def test_get_note_empty(self, db_session, sample_paper): result = get_note(db_session, "2401.12345") assert result["content"] == "" assert result["updated_at"] is None def test_get_note_paper_not_found(self, db_session): result = get_note(db_session, "nonexistent") assert result is None def test_save_note_paper_not_found(self, db_session): with pytest.raises(NotFoundError): save_note(db_session, "nonexistent", "内容") # ═══════════════════════════════════════════════════════════════════════ # 用户数据路由 HTTP 测试 # ═══════════════════════════════════════════════════════════════════════ class TestUserDataRoutes: """HTTP 级别的用户数据 API 测试。""" def test_bookmark_toggle_api(self, client, sample_paper): """POST /api/bookmark/{arxiv_id} 切换收藏。""" resp = client.post("/api/bookmark/2401.12345") assert resp.status_code == 200 data = resp.json() assert data["bookmarked"] is True # 再次切换 → 取消 resp2 = client.post("/api/bookmark/2401.12345") assert resp2.status_code == 200 data2 = resp2.json() assert data2["bookmarked"] is False def test_bookmark_htmx_returns_html(self, client, sample_paper): """HTMX 请求返回 HTML 片段。""" headers = {"HX-Request": "true"} resp = client.post("/api/bookmark/2401.12345", headers=headers) assert resp.status_code == 200 assert "btn-bookmark" in resp.text assert "★" in resp.text def test_bookmark_not_found(self, client): """收藏不存在的论文返回 404。""" resp = client.post("/api/bookmark/nonexistent") assert resp.status_code == 404 def test_reading_status_api(self, client, sample_paper): """POST /api/reading-status/{arxiv_id} 更新状态。""" resp = client.post( "/api/reading-status/2401.12345", json={"status": "read_summary"}, ) assert resp.status_code == 200 data = resp.json() assert data["status"] == "read_summary" def test_reading_status_invalid(self, client, sample_paper): """无效状态返回 400 (ValidationError)。""" resp = client.post( "/api/reading-status/2401.12345", json={"status": "invalid"}, ) assert resp.status_code == 400 def test_reading_status_not_found(self, client): """不存在的论文返回 404。""" resp = client.post( "/api/reading-status/nonexistent", json={"status": "unread"}, ) assert resp.status_code == 404 def test_note_get_api(self, client, sample_paper): """GET /api/note/{arxiv_id} 返回笔记。""" resp = client.get("/api/note/2401.12345") assert resp.status_code == 200 data = resp.json() assert data["content"] == "" def test_note_save_api(self, client, sample_paper): """POST /api/note/{arxiv_id} 保存笔记。""" resp = client.post( "/api/note/2401.12345", json={"content": "Markdown **笔记**"}, ) assert resp.status_code == 200 data = resp.json() assert data["content"] == "Markdown **笔记**" assert data["updated_at"] is not None # 读取确认 resp2 = client.get("/api/note/2401.12345") assert resp2.json()["content"] == "Markdown **笔记**" def test_note_not_found(self, client): """不存在的论文返回 404。""" resp = client.get("/api/note/nonexistent") assert resp.status_code == 404 resp2 = client.post( "/api/note/nonexistent", json={"content": "test"}, ) assert resp2.status_code == 404