| import asyncio |
|
|
| from sqlalchemy import select |
|
|
| from src.database.db import session_pool |
| from src.model import Paper |
|
|
|
|
| def build_paper_payload(**overrides): |
| payload = { |
| "title": "Attention Is All You Need", |
| "abstract": "Transformers replace recurrence with attention.", |
| "authors": "A. Researcher, B. Scientist", |
| "arxiv_id": "1706.03762", |
| "pdf_url": "https://example.com/papers/attention.pdf", |
| "paper_url": "https://arxiv.org/abs/1706.03762", |
| "github_url": "https://github.com/example/attention", |
| "topics": "transformers, attention", |
| "published_date": "2017-06-12", |
| "institution": "Example Lab", |
| "date_published": "2017-06-12", |
| "paper_source": "arxiv", |
| } |
| payload.update(overrides) |
| return payload |
|
|
|
|
| def test_list_papers_returns_empty_list(client, create_authenticated_user): |
| auth_user = create_authenticated_user() |
|
|
| response = client.get("/api/v1/papers/", headers=auth_user["headers"]) |
|
|
| assert response.status_code == 200 |
| assert response.json() == [] |
|
|
|
|
| def test_create_paper_and_fetch_by_id( |
| client, create_authenticated_user, external_service_spies |
| ): |
| auth_user = create_authenticated_user() |
|
|
| create_response = client.post( |
| "/api/v1/papers/", |
| headers=auth_user["headers"], |
| json=build_paper_payload(), |
| ) |
|
|
| assert create_response.status_code == 200 |
| created_paper = create_response.json() |
| assert created_paper["title"] == "Attention Is All You Need" |
| assert created_paper["thumbnail_url"] == "https://thumbs.example/1.png" |
| assert external_service_spies["background_index"] |
|
|
| get_response = client.get( |
| f"/api/v1/papers/{created_paper['id']}", |
| headers=auth_user["headers"], |
| ) |
| assert get_response.status_code == 200 |
| assert get_response.json()["arxiv_id"] == "1706.03762" |
|
|
|
|
| def test_protected_paper_route_rejects_invalid_token(client): |
| response = client.get( |
| "/api/v1/papers/", |
| headers={"Authorization": "Bearer not-a-real-token"}, |
| ) |
|
|
| assert response.status_code == 401 |
| assert response.json()["detail"] == "Could not validate credentials" |
|
|
|
|
| def test_create_paper_rejects_duplicate_arxiv_id(client, create_authenticated_user): |
| first_user = create_authenticated_user() |
| second_user = create_authenticated_user() |
|
|
| first_response = client.post( |
| "/api/v1/papers/", |
| headers=first_user["headers"], |
| json=build_paper_payload(), |
| ) |
| assert first_response.status_code == 200 |
|
|
| duplicate_response = client.post( |
| "/api/v1/papers/", |
| headers=second_user["headers"], |
| json=build_paper_payload(title="Duplicate title"), |
| ) |
| assert duplicate_response.status_code == 400 |
| assert duplicate_response.json()["detail"] == "Paper already exists" |
|
|
|
|
| def test_thumbnail_generation_failure_is_non_fatal( |
| client, create_authenticated_user, monkeypatch |
| ): |
| auth_user = create_authenticated_user() |
|
|
| async def failing_thumbnail(*args, **kwargs): |
| raise RuntimeError("thumbnail failed") |
|
|
| monkeypatch.setattr( |
| "src.controller.paper.generate_first_page_thumbnail", failing_thumbnail |
| ) |
|
|
| response = client.post( |
| "/api/v1/papers/", |
| headers=auth_user["headers"], |
| json=build_paper_payload(arxiv_id="2401.00001"), |
| ) |
|
|
| assert response.status_code == 200 |
| assert response.json()["thumbnail_url"] is None |
|
|
|
|
| def test_fetching_other_users_paper_returns_404( |
| client, create_authenticated_user, create_paper_record |
| ): |
| owner = create_authenticated_user() |
| stranger = create_authenticated_user() |
| paper = create_paper_record(user_id=owner["id"], arxiv_id="2401.00002") |
|
|
| response = client.get( |
| f"/api/v1/papers/{paper['id']}", |
| headers=stranger["headers"], |
| ) |
|
|
| assert response.status_code == 404 |
| assert response.json()["detail"] == "Paper not found" |
|
|
|
|
| def test_delete_paper_is_non_fatal_when_cleanup_fails( |
| client, create_authenticated_user, create_paper_record, monkeypatch |
| ): |
| auth_user = create_authenticated_user() |
| paper = create_paper_record(user_id=auth_user["id"], arxiv_id="2401.00003") |
|
|
| async def failing_cleanup(arxiv_ids): |
| raise RuntimeError("cleanup failed") |
|
|
| monkeypatch.setattr( |
| "src.controller.paper.IngestionEngine.delete_paper_using_paper_ids", |
| failing_cleanup, |
| ) |
|
|
| response = client.delete( |
| f"/api/v1/papers/{paper['id']}", |
| headers=auth_user["headers"], |
| ) |
|
|
| assert response.status_code == 204 |
|
|
| async def _load_paper(): |
| async with session_pool() as session: |
| return ( |
| await session.execute(select(Paper).where(Paper.id == paper["id"])) |
| ).scalar_one_or_none() |
|
|
| assert asyncio.run(_load_paper()) is None |
|
|
|
|
| def test_bulk_delete_returns_404_for_missing_papers(client, create_authenticated_user): |
| auth_user = create_authenticated_user() |
|
|
| response = client.delete( |
| "/api/v1/papers/bulk?paper_ids=999&paper_ids=1000", |
| headers=auth_user["headers"], |
| ) |
|
|
| assert response.status_code == 404 |
| assert "Papers not found" in response.json()["detail"] |
|
|