"""DELETE /api/v1/translations/{job_id} — retrait d'un travail de l'historique. Contexte : un travail échoué ou oublié par le serveur (rétention 24 h en mémoire) restait affiché dans « Traductions récentes » sans aucun moyen de le retirer, et sa page de relecture était vide. La route supprime le travail de la mémoire, sa clé Redis et ses segments de relecture. """ import pytest from fastapi.testclient import TestClient from unittest.mock import patch from main import app from routes.translate_routes import get_authenticated_user, _translation_jobs @pytest.fixture() def client(monkeypatch): from middleware.rate_limiting import RateLimitMiddleware async def _dispatch(self, request, call_next): return await call_next(request) monkeypatch.setattr(RateLimitMiddleware, "dispatch", _dispatch) return TestClient(app) class MockUser: def __init__(self, user_id="user_123"): self.id = user_id self.plan = "free" self.docs_translated_this_month = 0 self.pages_translated_this_month = 0 self.extra_credits = 0 def _make_job(job_id="tr_del1", user_id="user_123", status="completed"): return { "id": job_id, "user_id": user_id, "status": status, "file_name": "doc.docx", "created_at": "2026-09-01T10:00:00", "progress_percent": 100, } @pytest.fixture(autouse=True) def _clean_jobs(): saved = dict(_translation_jobs) _translation_jobs.clear() yield _translation_jobs.clear() _translation_jobs.update(saved) def _seed_segments(job_id, user_id="user_123", count=2): from database.models import Base, TranslationSegment from database.connection import sync_engine, get_sync_session Base.metadata.create_all(bind=sync_engine) with get_sync_session() as session: for i in range(count): session.add( TranslationSegment( job_id=job_id, user_id=user_id, segment_index=i, source_text=f"Hello {i}", translated_text=f"Bonjour {i}", status="pending", ) ) session.commit() class TestDeleteTranslationHistory: def test_owner_deletes_completed_job(self, client): app.dependency_overrides[get_authenticated_user] = _async_user(MockUser()) _translation_jobs["tr_del1"] = _make_job() _seed_segments("tr_del1") res = client.delete("/api/v1/translations/tr_del1") assert res.status_code == 200 body = res.json()["data"] assert body["deleted"] is True assert body["segments_removed"] == 2 assert "tr_del1" not in _translation_jobs from database.connection import get_sync_session from database.models import TranslationSegment with get_sync_session() as session: remaining = ( session.query(TranslationSegment) .filter(TranslationSegment.job_id == "tr_del1") .count() ) assert remaining == 0 app.dependency_overrides.clear() def test_delete_is_idempotent_when_job_already_gone(self, client): app.dependency_overrides[get_authenticated_user] = _async_user(MockUser()) # Le serveur a oublié le travail (rétention expirée / redémarrage) : # le client veut quand même nettoyer sa liste locale. res = client.delete("/api/v1/translations/tr_unknown") assert res.status_code == 200 assert res.json()["data"]["deleted"] is True app.dependency_overrides.clear() def test_other_users_job_is_404(self, client): app.dependency_overrides[get_authenticated_user] = _async_user(MockUser()) _translation_jobs["tr_someone"] = _make_job("tr_someone", user_id="user_999") res = client.delete("/api/v1/translations/tr_someone") assert res.status_code == 404 # Le travail reste intact assert "tr_someone" in _translation_jobs app.dependency_overrides.clear() def test_running_job_requires_cancel_first(self, client): app.dependency_overrides[get_authenticated_user] = _async_user(MockUser()) _translation_jobs["tr_run"] = _make_job("tr_run", status="processing") res = client.delete("/api/v1/translations/tr_run") assert res.status_code == 409 assert res.json()["error"] == "JOB_IN_PROGRESS" assert "tr_run" in _translation_jobs app.dependency_overrides.clear() def test_unauthenticated_is_401(self, client): async def _anonymous(): return None app.dependency_overrides[get_authenticated_user] = _anonymous _translation_jobs["tr_anon"] = _make_job() res = client.delete("/api/v1/translations/tr_anon") assert res.status_code == 401 app.dependency_overrides.clear() def _async_user(user): async def _auth(): return user return _auth