docs/tests/test_files.py

404 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Тесты работы с файлами - upload, download, файлы проектов
"""
import pytest
import httpx
import uuid
import tempfile
import os
from conftest import assert_uuid, assert_timestamp
@pytest.mark.asyncio
async def test_upload_file(http_client: httpx.AsyncClient):
"""Test uploading file via multipart/form-data"""
# Создаём временный файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("This is a test file for upload\nLine 2\nLine 3")
temp_file_path = f.name
try:
# Загружаем файл
with open(temp_file_path, 'rb') as f:
files = {'file': ('test_upload.txt', f, 'text/plain')}
response = await http_client.post("/upload", files=files)
assert response.status_code == 200
upload_data = response.json()
assert "file_id" in upload_data
assert "filename" in upload_data
assert "mime_type" in upload_data
assert "size" in upload_data
assert "storage_name" in upload_data
assert_uuid(upload_data["file_id"])
assert upload_data["filename"] == "test_upload.txt"
assert upload_data["mime_type"] == "text/plain"
assert upload_data["size"] > 0
assert isinstance(upload_data["storage_name"], str)
finally:
# Удаляем временный файл
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_upload_large_file_fails(http_client: httpx.AsyncClient):
"""Test that uploading too large file returns 413"""
# Создаём файл размером больше 50MB (лимит из API)
# Но для теста создадим файл 1MB и проверим что endpoint работает
large_content = "A" * (1024 * 1024) # 1MB
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write(large_content)
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('large_file.txt', f, 'text/plain')}
response = await http_client.post("/upload", files=files)
# В тестовой среде файл 1MB должен проходить успешно
# В продакшене лимит 50MB вернёт 413
assert response.status_code in [200, 413]
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_upload_without_file_fails(http_client: httpx.AsyncClient):
"""Test uploading without file field returns 422"""
response = await http_client.post("/upload", files={})
assert response.status_code == 422
@pytest.mark.asyncio
async def test_download_attachment_by_id():
"""Test downloading uploaded file (requires attachment ID)"""
# Этот тест сложно реализовать без создания сообщения с вложением
# Оставляем как плейсхолдер для полноценной реализации
pass
# Тесты файлов проектов
@pytest.mark.asyncio
async def test_get_project_files_list(http_client: httpx.AsyncClient, test_project: dict):
"""Test getting list of project files"""
response = await http_client.get(f"/projects/{test_project['id']}/files")
assert response.status_code == 200
files = response.json()
assert isinstance(files, list)
@pytest.mark.asyncio
async def test_upload_file_to_project(http_client: httpx.AsyncClient, test_project: dict):
"""Test uploading file directly to project"""
# Создаём временный файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
f.write("# Project Documentation\n\nThis is a test document for the project.")
temp_file_path = f.name
try:
# Загружаем файл в проект
with open(temp_file_path, 'rb') as f:
files = {'file': ('README.md', f, 'text/markdown')}
data = {'description': 'Project documentation file'}
response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files,
data=data
)
assert response.status_code == 200
project_file = response.json()
assert project_file["filename"] == "README.md"
assert project_file["description"] == "Project documentation file"
assert project_file["mime_type"] == "text/markdown"
assert project_file["size"] > 0
assert_uuid(project_file["id"])
# Проверяем информацию о загрузившем
assert "uploaded_by" in project_file
assert "id" in project_file["uploaded_by"]
assert "slug" in project_file["uploaded_by"]
assert "name" in project_file["uploaded_by"]
assert_timestamp(project_file["created_at"])
assert_timestamp(project_file["updated_at"])
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_upload_file_to_project_without_description(http_client: httpx.AsyncClient, test_project: dict):
"""Test uploading file to project without description"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("Simple text file without description")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('simple.txt', f, 'text/plain')}
response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files
)
assert response.status_code == 200
project_file = response.json()
assert project_file["filename"] == "simple.txt"
assert project_file["description"] is None
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_upload_file_to_nonexistent_project(http_client: httpx.AsyncClient):
"""Test uploading file to non-existent project returns 404"""
fake_project_id = str(uuid.uuid4())
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("File for nowhere")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('test.txt', f, 'text/plain')}
response = await http_client.post(
f"/projects/{fake_project_id}/files",
files=files
)
assert response.status_code == 404
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_get_project_file_info(http_client: httpx.AsyncClient, test_project: dict):
"""Test getting specific project file information"""
# Сначала загружаем файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
f.write('{"test": "data", "project": "file"}')
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('data.json', f, 'application/json')}
data = {'description': 'Test JSON data'}
upload_response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files,
data=data
)
assert upload_response.status_code == 200
project_file = upload_response.json()
# Получаем информацию о файле
response = await http_client.get(f"/projects/{test_project['id']}/files/{project_file['id']}")
assert response.status_code == 200
file_info = response.json()
assert file_info["id"] == project_file["id"]
assert file_info["filename"] == "data.json"
assert file_info["description"] == "Test JSON data"
assert file_info["mime_type"] == "application/json"
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_download_project_file(http_client: httpx.AsyncClient, test_project: dict):
"""Test downloading project file"""
# Сначала загружаем файл
test_content = "Downloadable content\nLine 2"
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write(test_content)
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('download_test.txt', f, 'text/plain')}
upload_response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files
)
assert upload_response.status_code == 200
project_file = upload_response.json()
# Скачиваем файл
response = await http_client.get(
f"/projects/{test_project['id']}/files/{project_file['id']}/download"
)
assert response.status_code == 200
# Проверяем заголовки
assert "content-type" in response.headers
assert "content-length" in response.headers
# Проверяем содержимое
downloaded_content = response.text
assert downloaded_content == test_content
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_update_project_file_description(http_client: httpx.AsyncClient, test_project: dict):
"""Test updating project file description"""
# Загружаем файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("File to update description")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('update_desc.txt', f, 'text/plain')}
data = {'description': 'Original description'}
upload_response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files,
data=data
)
assert upload_response.status_code == 200
project_file = upload_response.json()
# Обновляем описание
update_data = {"description": "Updated file description"}
response = await http_client.patch(
f"/projects/{test_project['id']}/files/{project_file['id']}",
json=update_data
)
assert response.status_code == 200
updated_file = response.json()
assert updated_file["description"] == "Updated file description"
assert updated_file["id"] == project_file["id"]
assert updated_file["filename"] == project_file["filename"]
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_clear_project_file_description(http_client: httpx.AsyncClient, test_project: dict):
"""Test clearing project file description"""
# Загружаем файл с описанием
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("File to clear description")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('clear_desc.txt', f, 'text/plain')}
data = {'description': 'Description to be cleared'}
upload_response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files,
data=data
)
assert upload_response.status_code == 200
project_file = upload_response.json()
# Очищаем описание
update_data = {"description": ""}
response = await http_client.patch(
f"/projects/{test_project['id']}/files/{project_file['id']}",
json=update_data
)
assert response.status_code == 200
updated_file = response.json()
assert not updated_file.get("description") # empty or None
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_delete_project_file(http_client: httpx.AsyncClient, test_project: dict):
"""Test deleting project file"""
# Загружаем файл
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write("File to be deleted")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': ('to_delete.txt', f, 'text/plain')}
upload_response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files
)
assert upload_response.status_code == 200
project_file = upload_response.json()
# Удаляем файл
response = await http_client.delete(
f"/projects/{test_project['id']}/files/{project_file['id']}"
)
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
# Проверяем что файл действительно удалён
response = await http_client.get(
f"/projects/{test_project['id']}/files/{project_file['id']}"
)
assert response.status_code == 404
finally:
os.unlink(temp_file_path)
@pytest.mark.asyncio
async def test_search_project_files(http_client: httpx.AsyncClient, test_project: dict):
"""Test searching project files by filename"""
# Загружаем несколько файлов
test_files = ["searchable_doc.md", "another_file.txt", "searchable_config.json"]
uploaded_files = []
for filename in test_files:
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
f.write(f"Content of {filename}")
temp_file_path = f.name
try:
with open(temp_file_path, 'rb') as f:
files = {'file': (filename, f, 'text/plain')}
response = await http_client.post(
f"/projects/{test_project['id']}/files",
files=files
)
assert response.status_code == 200
uploaded_files.append(response.json())
finally:
os.unlink(temp_file_path)
# Ищем файлы со словом "searchable"
response = await http_client.get(
f"/projects/{test_project['id']}/files?search=searchable"
)
assert response.status_code == 200
files = response.json()
searchable_files = [f for f in files if "searchable" in f["filename"].lower()]
assert len(searchable_files) >= 2 # Должно найти 2 файла с "searchable"