Files
office_translator/tests/test_translate_endpoint.py
sepehr 81cb4e09b7
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m8s
fix(tests): update PDF format test to use truly unsupported format (.txt)
2026-06-14 19:50:44 +02:00

892 lines
31 KiB
Python

"""
Tests pour POST /api/v1/translate
Couvre les AC 1-10 de la story 2.10 : Endpoint POST /api/v1/translate (Core)
"""
import io
import time
from pathlib import Path
from unittest.mock import patch, MagicMock, AsyncMock
from zipfile import ZipFile
import pytest
from fastapi.testclient import TestClient
TRANSLATE_URL = "/api/v1/translate"
STATUS_URL = "/api/v1/translations"
REGISTER_URL = "/api/v1/auth/register"
LOGIN_URL = "/api/v1/auth/login"
VALID_USER = {
"email": "translate@example.com",
"password": "Password123!",
"name": "Translate User",
}
def create_valid_excel() -> bytes:
"""Create a minimal valid .xlsx file (ZIP with office content)."""
buf = io.BytesIO()
with ZipFile(buf, "w") as zf:
zf.writestr(
"[Content_Types].xml",
'<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
)
zf.writestr(
"_rels/.rels",
'<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
)
zf.writestr(
"xl/workbook.xml",
'<?xml version="1.0"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"></workbook>',
)
buf.seek(0)
return buf.read()
def create_valid_docx() -> bytes:
"""Create a minimal valid .docx file."""
buf = io.BytesIO()
with ZipFile(buf, "w") as zf:
zf.writestr(
"[Content_Types].xml",
'<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
)
zf.writestr(
"_rels/.rels",
'<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
)
zf.writestr(
"word/document.xml",
'<?xml version="1.0"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"></w:document>',
)
buf.seek(0)
return buf.read()
def create_valid_pptx() -> bytes:
"""Create a minimal valid .pptx file."""
buf = io.BytesIO()
with ZipFile(buf, "w") as zf:
zf.writestr(
"[Content_Types].xml",
'<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"></Types>',
)
zf.writestr(
"_rels/.rels",
'<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>',
)
zf.writestr(
"ppt/presentation.xml",
'<?xml version="1.0"?><p:presentation xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"></p:presentation>',
)
buf.seek(0)
return buf.read()
def create_invalid_file() -> bytes:
"""Create an invalid file (not a ZIP/Office document)."""
return b"This is not a valid office document"
@pytest.fixture()
def users_file(tmp_path: Path) -> Path:
"""Fichier de stockage JSON isole pour les tests."""
return tmp_path / "users.json"
@pytest.fixture()
def client(users_file: Path, monkeypatch):
"""TestClient avec stockage JSON isole et rate limiting desactive."""
import services.auth_service as auth_svc
monkeypatch.setattr(auth_svc, "USERS_FILE", users_file)
monkeypatch.setattr(auth_svc, "USE_DATABASE", False)
monkeypatch.setattr(auth_svc, "DATABASE_AVAILABLE", False)
from middleware.rate_limiting import RateLimitManager
async def _check_request_allow(self, request):
return True, "ok", "test"
async def _check_translation_allow(self, request, file_size_mb=0):
return True, "ok"
monkeypatch.setattr(RateLimitManager, "check_request", _check_request_allow)
monkeypatch.setattr(RateLimitManager, "check_translation", _check_translation_allow)
def _check_usage_limits_allow(user):
return {
"can_translate": True,
"docs_used": 0,
"docs_limit": 5,
"docs_remaining": 5,
"pages_used": 0,
"extra_credits": 0,
"max_pages_per_doc": 50,
"max_file_size_mb": 10,
"allowed_providers": ["google", "deepl"],
}
monkeypatch.setattr(
"routes.translate_routes.check_usage_limits", _check_usage_limits_allow
)
from main import app
return TestClient(app, raise_server_exceptions=True)
@pytest.fixture()
def authenticated_client(client):
"""Client avec un utilisateur enregistre et authentifie."""
client.post(REGISTER_URL, json=VALID_USER)
response = client.post(
LOGIN_URL,
json={
"email": VALID_USER["email"],
"password": VALID_USER["password"],
},
)
token = response.json()["data"]["access_token"]
client.headers["Authorization"] = f"Bearer {token}"
return client
# ---------------------------------------------------------------------------
# AC2: File Upload
# ---------------------------------------------------------------------------
class TestFileUpload:
"""AC2: POST to /api/v1/translate accepts multipart/form-data"""
def test_accepts_multipart_form_data(self, authenticated_client):
"""Endpoint accepts multipart/form-data with file, source_lang, target_lang"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_requires_file_or_url(self, client):
"""Returns error if neither file nor file_url provided"""
response = client.post(
TRANSLATE_URL,
data={"target_lang": "fr"},
)
assert response.status_code == 400
body = response.json()
assert body["error"] == "MISSING_FILE"
def test_accepts_source_and_target_lang(self, authenticated_client):
"""Accepts source_lang and target_lang parameters"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"source_lang": "en", "target_lang": "fr"},
)
assert response.status_code == 202
body = response.json()
assert body["data"]["source_lang"] == "en"
assert body["data"]["target_lang"] == "fr"
# ---------------------------------------------------------------------------
# AC3 & AC5: File Validation
# ---------------------------------------------------------------------------
class TestFileValidation:
"""AC3, AC5: System validates format (xlsx/docx/pptx only), max size 50MB, magic bytes"""
def test_rejects_invalid_format_pdf(self, authenticated_client):
"""AC5: Unsupported formats return 400 with INVALID_FORMAT"""
invalid_content = create_invalid_file()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": ("test.txt", io.BytesIO(invalid_content), "text/plain")
},
data={"target_lang": "fr"},
)
assert response.status_code == 400
body = response.json()
assert body["error"] == "INVALID_FORMAT"
def test_rejects_invalid_magic_bytes(self, authenticated_client):
"""AC3/AC4: Checks magic bytes, returns CORRUPTED_FILE for invalid content"""
invalid_content = create_invalid_file()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"fake.xlsx",
io.BytesIO(invalid_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 400
body = response.json()
assert body["error"] == "CORRUPTED_FILE"
def test_accepts_xlsx(self, authenticated_client):
"""Accepts .xlsx files"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_accepts_docx(self, authenticated_client):
"""Accepts .docx files"""
docx_content = create_valid_docx()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.docx",
io.BytesIO(docx_content),
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_accepts_pptx(self, authenticated_client):
"""Accepts .pptx files"""
pptx_content = create_valid_pptx()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.pptx",
io.BytesIO(pptx_content),
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_error_includes_accepted_formats(self, authenticated_client):
"""AC5: Error includes accepted formats list"""
invalid_content = create_invalid_file()
response = authenticated_client.post(
TRANSLATE_URL,
files={"file": ("test.txt", io.BytesIO(invalid_content), "text/plain")},
data={"target_lang": "fr"},
)
body = response.json()
assert "accepted_formats" in body.get("details", {}) or ".xlsx" in str(body)
# ---------------------------------------------------------------------------
# AC7: File Too Large
# ---------------------------------------------------------------------------
class TestFileTooLarge:
"""AC7: Files > 50MB return 413 with FILE_TOO_LARGE"""
def test_returns_413_for_large_file(self, authenticated_client, monkeypatch):
"""Files exceeding max size return 413"""
from middleware.validation import FileValidator
# Create a validator with very small limit for testing
small_validator = FileValidator(
max_size_mb=0.001, allowed_extensions={".xlsx", ".docx", ".pptx"}
)
monkeypatch.setattr("routes.translate_routes.file_validator", small_validator)
large_content = b"x" * 2000 # 2KB > 1KB limit
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"large.xlsx",
io.BytesIO(large_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 413
body = response.json()
assert body["error"] == "FILE_TOO_LARGE"
# ---------------------------------------------------------------------------
# AC4: Success Response
# ---------------------------------------------------------------------------
class TestSuccessResponse:
"""AC4: Valid requests return HTTP 202 with proper format"""
def test_returns_202_on_success(self, authenticated_client):
"""Returns HTTP 202 Accepted"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_response_has_data_with_id(self, authenticated_client):
"""Response contains data.id"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
body = response.json()
assert "data" in body
assert "id" in body["data"]
assert body["data"]["id"].startswith("tr_")
def test_response_has_status_processing(self, authenticated_client):
"""Response contains status 'processing'"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
body = response.json()
assert body["data"]["status"] == "processing"
def test_response_has_meta_with_rate_limit(self, authenticated_client):
"""Response contains meta.rate_limit_remaining"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
body = response.json()
assert "meta" in body
assert "rate_limit_remaining" in body["meta"]
# ---------------------------------------------------------------------------
# AC1: Authentication
# ---------------------------------------------------------------------------
class TestAuthentication:
"""AC1: Endpoint requires valid JWT token or X-API-Key"""
def test_works_with_jwt_token(self, authenticated_client):
"""Accepts JWT Bearer token"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
def test_works_without_auth(self, client):
"""Allows unauthenticated requests (but with tier limits)"""
excel_content = create_valid_excel()
response = client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
# Should still work without auth
assert response.status_code == 202
# ---------------------------------------------------------------------------
# AC6: Quota Exceeded
# ---------------------------------------------------------------------------
class TestQuotaExceeded:
"""AC6: Users exceeding tier limit return 429"""
def test_returns_429_when_quota_exceeded(self, client, monkeypatch):
"""Returns 429 with QUOTA_EXCEEDED when quota exceeded"""
def _check_usage_limits_denied(user):
return {
"can_translate": False,
"docs_used": 5,
"docs_limit": 5,
"docs_remaining": 0,
"pages_used": 0,
"extra_credits": 0,
"max_pages_per_doc": 50,
"max_file_size_mb": 10,
"allowed_providers": ["google", "deepl"],
}
monkeypatch.setattr(
"routes.translate_routes.check_usage_limits", _check_usage_limits_denied
)
# Register and login
client.post(REGISTER_URL, json=VALID_USER)
response = client.post(
LOGIN_URL,
json={
"email": VALID_USER["email"],
"password": VALID_USER["password"],
},
)
token = response.json()["data"]["access_token"]
client.headers["Authorization"] = f"Bearer {token}"
excel_content = create_valid_excel()
response = client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 429
body = response.json()
# HTTPException returns detail dict with error field
assert (
body.get("error") == "QUOTA_EXCEEDED"
or body.get("detail", {}).get("error") == "QUOTA_EXCEEDED"
)
def test_includes_retry_after_header(self, client, monkeypatch):
"""Includes Retry-After header on 429"""
def _check_usage_limits_denied(user):
return {
"can_translate": False,
"docs_used": 5,
"docs_limit": 5,
"docs_remaining": 0,
"pages_used": 0,
"extra_credits": 0,
"max_pages_per_doc": 50,
"max_file_size_mb": 10,
"allowed_providers": ["google", "deepl"],
}
monkeypatch.setattr(
"routes.translate_routes.check_usage_limits", _check_usage_limits_denied
)
client.post(REGISTER_URL, json=VALID_USER)
response = client.post(
LOGIN_URL,
json={
"email": VALID_USER["email"],
"password": VALID_USER["password"],
},
)
token = response.json()["data"]["access_token"]
client.headers["Authorization"] = f"Bearer {token}"
excel_content = create_valid_excel()
response = client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert "retry-after" in response.headers
# ---------------------------------------------------------------------------
# AC10: Optional Parameters
# ---------------------------------------------------------------------------
class TestOptionalParameters:
"""AC10: Support mode, provider, webhook_url, glossary_id, custom_prompt"""
def test_accepts_mode_classic(self, authenticated_client):
"""Accepts mode='classic'"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "mode": "classic"},
)
assert response.status_code == 202
def test_accepts_mode_llm(self, authenticated_client):
"""Accepts mode='llm'"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "mode": "llm"},
)
assert response.status_code == 202
def test_accepts_webhook_url(self, authenticated_client):
"""Accepts webhook_url parameter"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "webhook_url": "https://example.com/webhook"},
)
assert response.status_code == 202
# ---------------------------------------------------------------------------
# AC8: Async Processing
# ---------------------------------------------------------------------------
class TestAsyncProcessing:
"""AC8: Translation is processed asynchronously"""
def test_returns_immediately_with_job_id(self, authenticated_client):
"""Endpoint returns 202 immediately with job ID"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
assert response.status_code == 202
body = response.json()
assert body["data"]["status"] == "processing"
assert body["data"]["id"].startswith("tr_")
def test_can_check_job_status(self, authenticated_client):
"""Can check job status via GET /api/v1/translations/{id}"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
)
job_id = response.json()["data"]["id"]
status_response = authenticated_client.get(f"{STATUS_URL}/{job_id}")
assert status_response.status_code == 200
body = status_response.json()
assert "data" in body
assert body["data"]["id"] == job_id
def test_returns_404_for_unknown_job(self, authenticated_client):
"""Returns 404 for unknown job ID"""
response = authenticated_client.get(f"{STATUS_URL}/tr_unknown123")
assert response.status_code == 404
# ---------------------------------------------------------------------------
# AC9: URL Ingestion (Pro)
# ---------------------------------------------------------------------------
class TestURLIngestion:
"""AC9: Pro users can provide file_url parameter instead of file upload"""
def test_pro_feature_requires_pro_tier(self, authenticated_client, monkeypatch):
"""file_url is a Pro-only feature"""
from models.subscription import User, PlanType
from datetime import datetime
# User is free tier by default, should get 403
excel_url = "https://example.com/test.xlsx"
response = authenticated_client.post(
TRANSLATE_URL,
data={"target_lang": "fr", "file_url": excel_url},
)
assert response.status_code == 403
body = response.json()
assert body["error"] == "PRO_FEATURE_REQUIRED"
def test_glossary_requires_pro(self, authenticated_client):
"""glossary_id is a Pro-only feature"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "glossary_id": "some-glossary-id"},
)
assert response.status_code == 403
body = response.json()
assert body["error"] == "PRO_FEATURE_REQUIRED"
def test_custom_prompt_requires_pro(self, authenticated_client):
"""custom_prompt is a Pro-only feature"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "custom_prompt": "Translate formally"},
)
assert response.status_code == 403
body = response.json()
assert body["error"] == "PRO_FEATURE_REQUIRED"
# ---------------------------------------------------------------------------
# Additional Tests for Code Review Issues
# ---------------------------------------------------------------------------
class TestProviderParameter:
"""AC10: Provider parameter support"""
def test_accepts_provider_google(self, authenticated_client):
"""Accepts provider='google'"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "provider": "google"},
)
assert response.status_code == 202
class TestSourceLangValidation:
"""Source language validation"""
def test_invalid_source_lang_returns_400(self, authenticated_client):
"""Invalid source_lang returns 400"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "source_lang": "invalid_code_xyz"},
)
assert response.status_code == 400
body = response.json()
assert body["error"] == "INVALID_FORMAT"
class TestWebhookValidation:
"""Webhook URL validation"""
def test_invalid_webhook_url_returns_400(self, authenticated_client):
"""Invalid webhook_url returns 400"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "webhook_url": "not-a-valid-url"},
)
assert response.status_code == 400
body = response.json()
assert body["error"] == "INVALID_WEBHOOK_URL"
def test_valid_webhook_url_accepted(self, authenticated_client):
"""Valid webhook_url is accepted"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "webhook_url": "https://example.com/webhook"},
)
assert response.status_code == 202
class TestNoHTTP500:
"""NFR12: Zero HTTP 500 - all errors should be 4xx"""
def test_unexpected_error_returns_400_not_500(
self, authenticated_client, monkeypatch
):
"""Unexpected errors return 400, not 500"""
from routes import translate_routes
async def _failing_validate(*args, **kwargs):
raise RuntimeError("Unexpected error")
monkeypatch.setattr(
translate_routes.file_validator, "validate_async", _failing_validate
)
response = authenticated_client.post(
TRANSLATE_URL,
files={"file": ("test.xlsx", io.BytesIO(b"fake"), "application/vnd...")},
data={"target_lang": "fr"},
)
assert response.status_code in [400, 413, 401, 403, 429]
class TestAPIKeyAuth:
"""AC1: X-API-Key header authentication"""
def test_api_key_auth_placeholder(self, client):
"""X-API-Key header is accepted (placeholder test)"""
excel_content = create_valid_excel()
response = client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr"},
headers={"X-API-Key": "test-api-key-placeholder"},
)
assert response.status_code in [202, 401]
class TestTranslateImagesParameter:
"""Test translate_images parameter in POST /api/v1/translate"""
def test_accepts_translate_images_parameter(self, authenticated_client):
"""Endpoint accepts translate_images form parameter"""
excel_content = create_valid_excel()
response = authenticated_client.post(
TRANSLATE_URL,
files={
"file": (
"test.xlsx",
io.BytesIO(excel_content),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
},
data={"target_lang": "fr", "translate_images": "true"},
)
assert response.status_code == 202