ResearchIT / tests /test_saved.py
siddhm11
Phase 3 complete: Hybrid Semantic Search pipeline
d5a6f3e
"""
Tests for GET /saved page.
Covers: empty state, paper listing, cookie, remove action, source logging.
"""
import asyncio
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def client(tmp_path, monkeypatch):
import app.config as cfg
import app.db as db_mod
db_path = str(tmp_path / "test.db")
monkeypatch.setattr(cfg, "DB_PATH", db_path)
monkeypatch.setattr(db_mod, "DB_PATH", db_path)
import app.user_state as us
us._cache.clear()
from app.qdrant_svc import _client
_client.cache_clear()
from app.main import app
asyncio.get_event_loop().run_until_complete(db_mod.init_db())
with TestClient(app, raise_server_exceptions=True) as c:
yield c
# ── Basic page behaviour ──────────────────────────────────────────────────────
def test_saved_page_returns_200(client):
resp = client.get("/saved")
assert resp.status_code == 200
def test_saved_page_sets_cookie(client):
resp = client.get("/saved")
assert "arxiv_user_id" in resp.cookies
def test_saved_page_empty_for_new_user(client):
"""New user has no saves β€” shows the empty-state message."""
resp = client.get("/saved")
assert resp.status_code == 200
assert "No saved papers" in resp.text
def test_saved_page_shows_paper_after_save(client, monkeypatch):
"""After saving a paper, it appears on the saved page."""
import app.arxiv_svc as arxiv
async def fake_batch(ids):
return {
"1706.03762": {
"arxiv_id": "1706.03762",
"title": "Attention Is All You Need",
"abstract": "The original transformer paper.",
"authors": '["Vaswani"]',
"category": "cs.CL",
"published": "2017-06-12",
"year": 2017,
}
}
monkeypatch.setattr(arxiv, "fetch_metadata_batch", fake_batch)
client.get("/")
client.post("/api/papers/1706.03762/save", data={"source": "search"})
resp = client.get("/saved")
assert resp.status_code == 200
assert "Attention Is All You Need" in resp.text
def test_saved_page_shows_correct_count(client, monkeypatch):
"""The count badge reflects the number of saved papers."""
import app.arxiv_svc as arxiv
papers = {
"1706.03762": {
"arxiv_id": "1706.03762", "title": "Attention Is All You Need", "abstract": "...",
"authors": '[]', "category": "cs.CL", "published": "2017-06-12", "year": 2017,
},
"1512.03385": {
"arxiv_id": "1512.03385", "title": "Deep Residual Learning for Image Recognition",
"abstract": "...", "authors": '[]', "category": "cs.CV",
"published": "2015-12-10", "year": 2015,
},
}
async def fake_batch(ids):
return {k: v for k, v in papers.items() if k in ids}
monkeypatch.setattr(arxiv, "fetch_metadata_batch", fake_batch)
client.get("/")
client.post("/api/papers/1706.03762/save", data={"source": "search"})
client.post("/api/papers/1512.03385/save", data={"source": "search"})
resp = client.get("/saved")
assert resp.status_code == 200
assert "2 saved" in resp.text
# ── Remove from saved ─────────────────────────────────────────────────────────
def test_remove_paper_updates_state(client):
"""Dismissing a saved paper removes it from positive_list and adds to negatives."""
import app.user_state as us
client.get("/")
user_id = client.cookies.get("arxiv_user_id")
client.post("/api/papers/1706.03762/save", data={"source": "search"})
state = us.get_user_state(user_id)
assert "1706.03762" in state.positive_list
client.post("/api/papers/1706.03762/not-interested", data={"source": "saved"})
state = us.get_user_state(user_id)
assert "1706.03762" not in state.positive_list
assert "1706.03762" in state.negative_list
def test_remove_returns_empty_response(client):
"""not-interested returns empty HTML (HTMX removes card)."""
client.get("/")
client.post("/api/papers/1706.03762/save", data={"source": "search"})
resp = client.post("/api/papers/1706.03762/not-interested", data={"source": "saved"})
assert resp.status_code == 200
assert resp.text.strip() == ""
# ── Source logging ────────────────────────────────────────────────────────────
def test_save_source_is_logged(client):
"""Source field on the save action is persisted to the DB."""
import app.db as db_mod
client.get("/")
user_id = client.cookies.get("arxiv_user_id")
client.post("/api/papers/1706.03762/save", data={"source": "search", "position": "2"})
rows = asyncio.get_event_loop().run_until_complete(
db_mod.get_user_interactions(user_id, event_types=["save"])
)
assert len(rows) == 1
assert rows[0]["paper_id"] == "1706.03762"
def test_dismiss_source_saved_is_logged(client):
"""Dismiss from saved page logs source='saved'."""
import app.db as db_mod
client.get("/")
user_id = client.cookies.get("arxiv_user_id")
client.post("/api/papers/1706.03762/save", data={"source": "search"})
client.post("/api/papers/1706.03762/not-interested", data={"source": "saved"})
rows = asyncio.get_event_loop().run_until_complete(
db_mod.get_user_interactions(user_id, event_types=["not_interested"])
)
assert len(rows) == 1
assert rows[0]["paper_id"] == "1706.03762"