Эндпоинты не работают

This commit is contained in:
2025-09-02 10:09:22 +03:00
parent 84069e4e41
commit de63f98b50
22 changed files with 1510 additions and 80 deletions

123
tests/README.md Normal file
View File

@@ -0,0 +1,123 @@
# API Endpoints Tests
Этот модуль содержит pytest тесты для всех API эндпоинтов проекта NIN Excel Parsers.
## Структура
```
tests/
├── __init__.py
├── conftest.py # Конфигурация pytest
├── test_all_endpoints.py # Основной файл для запуска всех тестов
├── test_upload_endpoints.py # Тесты API эндпоинтов загрузки данных
├── test_svodka_pm_endpoints.py # Тесты API svodka_pm эндпоинтов
├── test_svodka_ca_endpoints.py # Тесты API svodka_ca эндпоинтов
├── test_monitoring_fuel_endpoints.py # Тесты API monitoring_fuel эндпоинтов
├── test_parsers_direct.py # Прямое тестирование парсеров
├── test_upload_with_local_storage.py # Тестирование загрузки в локальный storage
├── test_getters_with_local_storage.py # Тестирование геттеров с локальными данными
├── test_data/ # Тестовые данные
│ ├── svodka_ca.xlsx
│ ├── pm_plan.zip
│ └── monitoring.zip
├── local_storage/ # Локальный storage (создается автоматически)
│ ├── data/ # Сохраненные DataFrame
│ └── metadata/ # Метаданные объектов
├── requirements.txt # Зависимости для тестов
└── README.md # Этот файл
```
## Установка зависимостей
```bash
pip install -r tests/requirements.txt
```
## Запуск тестов
### Запуск всех тестов
```bash
cd tests
python test_all_endpoints.py
```
### Запуск конкретных тестов
```bash
# API тесты (требуют запущенный сервер)
pytest test_upload_endpoints.py -v
pytest test_svodka_pm_endpoints.py -v
pytest test_svodka_ca_endpoints.py -v
pytest test_monitoring_fuel_endpoints.py -v
# Прямые тесты парсеров (не требуют сервер)
pytest test_parsers_direct.py -v
pytest test_upload_with_local_storage.py -v
pytest test_getters_with_local_storage.py -v
# Все тесты с локальным storage
pytest test_parsers_direct.py test_upload_with_local_storage.py test_getters_with_local_storage.py -v
```
## Предварительные условия
1. **API сервер должен быть запущен** на `http://localhost:8000` (только для API тестов)
2. **Тестовые данные** находятся в папке `test_data/`
3. **Локальный storage** используется для прямого тестирования парсеров
## Последовательность тестирования
### Вариант 1: API тесты (требуют запущенный сервер)
1. **Загрузка данных** (`test_upload_endpoints.py`)
- Загрузка `svodka_ca.xlsx`
- Загрузка `pm_plan.zip`
- Загрузка `monitoring.zip`
2. **Тестирование эндпоинтов** (в любом порядке)
- `test_svodka_pm_endpoints.py`
- `test_svodka_ca_endpoints.py`
- `test_monitoring_fuel_endpoints.py`
### Вариант 2: Прямые тесты (не требуют сервер)
1. **Тестирование парсеров** (`test_parsers_direct.py`)
- Проверка регистрации парсеров
- Проверка локального storage
2. **Загрузка в локальный storage** (`test_upload_with_local_storage.py`)
- Загрузка всех файлов в локальный storage
- Проверка сохранения данных
3. **Тестирование геттеров** (`test_getters_with_local_storage.py`)
- Тестирование всех геттеров с локальными данными
- Выявление проблем в логике парсеров
## Ожидаемые результаты
Все тесты должны возвращать **статус 200** и содержать поле `"success": true` в ответе.
## Примеры тестовых запросов
Тесты используют примеры из Pydantic схем:
### svodka_pm
```json
{
"id": "SNPZ",
"codes": [78, 79],
"columns": ["ПП", "СЭБ"]
}
```
### svodka_ca
```json
{
"modes": ["fact", "plan"],
"tables": ["table1", "table2"]
}
```
### monitoring_fuel
```json
{
"columns": ["total", "normativ"]
}
```

71
tests/TEST_RESULTS.md Normal file
View File

@@ -0,0 +1,71 @@
# Результаты тестирования API эндпоинтов
## Сводка
Создана полная система тестирования с локальным storage для проверки всех API эндпоинтов проекта NIN Excel Parsers.
## Структура тестов
### 1. Прямые тесты парсеров (`test_parsers_direct.py`)
-**Регистрация парсеров** - все парсеры корректно регистрируются
-**Локальный storage** - работает корректно
-**ReportService** - корректно работает с локальным storage
### 2. Тесты загрузки (`test_upload_with_local_storage.py`)
-**svodka_ca.xlsx** - парсер возвращает `None`
-**pm_plan.zip** - парсер возвращает словарь с `None` значениями
-**monitoring.zip** - парсер возвращает пустой словарь
### 3. Тесты геттеров (`test_getters_with_local_storage.py`)
-**Все геттеры** - не работают из-за проблем с загрузкой данных
### 4. API тесты (`test_*_endpoints.py`)
-**Загрузка файлов** - эндпоинты работают
-**Геттеры** - не работают из-за проблем с данными
## Выявленные проблемы
### 1. Парсер svodka_ca
- **Проблема**: Возвращает `None` вместо DataFrame
- **Причина**: Парсер не может обработать тестовый файл `svodka_ca.xlsx`
- **Статус**: Требует исправления
### 2. Парсер svodka_pm
- **Проблема**: Возвращает словарь с `None` значениями
- **Причина**: Файлы в архиве `pm_plan.zip` не найдены (неправильные имена файлов)
- **Статус**: Требует исправления логики поиска файлов
### 3. Парсер monitoring_fuel
- **Проблема**: Возвращает пустой словарь
- **Причина**: Ошибки при загрузке файлов - "None of ['id'] are in the columns"
- **Статус**: Требует исправления логики обработки колонок
## Рекомендации
### Немедленные действия
1. **Исправить парсер svodka_ca** - проверить логику парсинга Excel файлов
2. **Исправить парсер svodka_pm** - проверить логику поиска файлов в архиве
3. **Исправить парсер monitoring_fuel** - проверить логику обработки колонок
### Долгосрочные улучшения
1. **Улучшить обработку ошибок** в парсерах
2. **Добавить валидацию данных** перед сохранением
3. **Создать более детальные тесты** для каждого парсера
## Техническая информация
### Локальный storage
- ✅ Создан `LocalStorageAdapter` для тестирования
- ✅ Поддерживает все операции: save, load, delete, list
- ✅ Автоматически очищается после тестов
### Инфраструктура тестов
- ✅ Pytest конфигурация с фикстурами
- ✅ Автоматическая регистрация парсеров
- ✅ Поддержка как API, так и прямых тестов
## Заключение
Система тестирования создана и работает корректно. Выявлены конкретные проблемы в парсерах, которые требуют исправления. После исправления парсеров все тесты должны пройти успешно.
**Следующий шаг**: Исправить выявленные проблемы в парсерах согласно результатам отладочных тестов.

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Tests package

97
tests/conftest.py Normal file
View File

@@ -0,0 +1,97 @@
"""
Конфигурация pytest для тестирования API эндпоинтов
"""
import pytest
import requests
import time
import os
import sys
from pathlib import Path
# Добавляем путь к проекту для импорта модулей
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "python_parser"))
from adapters.local_storage import LocalStorageAdapter
# Базовый URL API
API_BASE_URL = "http://localhost:8000"
# Путь к тестовым данным
TEST_DATA_DIR = Path(__file__).parent / "test_data"
@pytest.fixture(scope="session")
def api_base_url():
"""Базовый URL для API"""
return API_BASE_URL
@pytest.fixture(scope="session")
def test_data_dir():
"""Директория с тестовыми данными"""
return TEST_DATA_DIR
@pytest.fixture(scope="session")
def wait_for_api():
"""Ожидание готовности API"""
max_attempts = 30
for attempt in range(max_attempts):
try:
response = requests.get(f"{API_BASE_URL}/docs", timeout=5)
if response.status_code == 200:
print(f"✅ API готов после {attempt + 1} попыток")
return True
except requests.exceptions.RequestException:
pass
if attempt < max_attempts - 1:
time.sleep(2)
pytest.fail("❌ API не готов после 30 попыток")
@pytest.fixture
def upload_file(test_data_dir):
"""Фикстура для загрузки файла"""
def _upload_file(filename):
file_path = test_data_dir / filename
if not file_path.exists():
pytest.skip(f"Файл {filename} не найден в {test_data_dir}")
return file_path
return _upload_file
@pytest.fixture(scope="session")
def local_storage():
"""Фикстура для локального storage"""
storage = LocalStorageAdapter("tests/local_storage")
yield storage
# Очищаем storage после всех тестов
storage.clear_all()
@pytest.fixture
def clean_storage(local_storage):
"""Фикстура для очистки storage перед каждым тестом"""
local_storage.clear_all()
yield local_storage
def make_api_request(url, method="GET", data=None, files=None, json_data=None):
"""Универсальная функция для API запросов"""
try:
if method.upper() == "GET":
response = requests.get(url, timeout=30)
elif method.upper() == "POST":
if files:
response = requests.post(url, files=files, timeout=30)
elif json_data:
response = requests.post(url, json=json_data, timeout=30)
else:
response = requests.post(url, data=data, timeout=30)
else:
raise ValueError(f"Неподдерживаемый метод: {method}")
return response
except requests.exceptions.RequestException as e:
pytest.fail(f"Ошибка API запроса: {e}")
@pytest.fixture
def api_request():
"""Фикстура для API запросов"""
return make_api_request

2
tests/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
pytest>=7.0.0
requests>=2.28.0

View File

@@ -0,0 +1,20 @@
"""
Основной файл для запуска всех тестов API эндпоинтов
"""
import pytest
import sys
from pathlib import Path
# Добавляем путь к проекту для импорта модулей
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "python_parser"))
if __name__ == "__main__":
# Запуск всех тестов
pytest.main([
__file__.replace("test_all_endpoints.py", ""),
"-v", # подробный вывод
"--tb=short", # короткий traceback
"--color=yes", # цветной вывод
"-x", # остановка на первой ошибке
])

Binary file not shown.

BIN
tests/test_data/pm_plan.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,339 @@
"""
Тестирование геттеров с данными из локального storage
"""
import pytest
import sys
from pathlib import Path
# Добавляем путь к проекту
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "python_parser"))
from core.services import ReportService, PARSERS
from core.models import DataRequest, UploadRequest
from adapters.local_storage import LocalStorageAdapter
from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser
# Регистрируем парсеры
PARSERS.update({
'svodka_pm': SvodkaPMParser,
'svodka_ca': SvodkaCAParser,
'monitoring_fuel': MonitoringFuelParser,
})
class TestGettersWithLocalStorage:
"""Тестирование геттеров с локальным storage"""
@pytest.fixture(autouse=True)
def setup_storage(self, clean_storage):
"""Настройка локального storage для каждого теста"""
self.storage = clean_storage
self.report_service = ReportService(self.storage)
def test_svodka_pm_single_og_with_local_data(self, upload_file):
"""Тест svodka_pm single_og с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("pm_plan.zip")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='svodka_pm',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='svodka_pm',
get_params={
'mode': 'single_og',
'id': 'SNPZ',
'codes': [78, 79],
'columns': ['ПП', 'СЭБ']
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ svodka_pm/single_og работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ svodka_pm/single_og не работает: {result.message}")
# Не делаем assert, чтобы увидеть все ошибки
def test_svodka_pm_total_ogs_with_local_data(self, upload_file):
"""Тест svodka_pm total_ogs с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("pm_plan.zip")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='svodka_pm',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='svodka_pm',
get_params={
'mode': 'total_ogs',
'codes': [78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
'columns': ['БП', 'ПП', 'СЭБ']
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ svodka_pm/total_ogs работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ svodka_pm/total_ogs не работает: {result.message}")
def test_svodka_ca_get_ca_data_with_local_data(self, upload_file):
"""Тест svodka_ca get_ca_data с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("svodka_ca.xlsx")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='svodka_ca',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='svodka_ca',
get_params={
'mode': 'get_ca_data',
'modes': ['fact', 'plan'],
'tables': ['table1', 'table2']
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ svodka_ca/get_ca_data работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ svodka_ca/get_ca_data не работает: {result.message}")
def test_monitoring_fuel_get_total_by_columns_with_local_data(self, upload_file):
"""Тест monitoring_fuel get_total_by_columns с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("monitoring.zip")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='monitoring_fuel',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='monitoring_fuel',
get_params={
'mode': 'total_by_columns',
'columns': ['total', 'normativ']
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ monitoring_fuel/get_total_by_columns работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ monitoring_fuel/get_total_by_columns не работает: {result.message}")
def test_monitoring_fuel_get_month_by_code_with_local_data(self, upload_file):
"""Тест monitoring_fuel get_month_by_code с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("monitoring.zip")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='monitoring_fuel',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='monitoring_fuel',
get_params={
'mode': 'month_by_code',
'month': '02'
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ monitoring_fuel/get_month_by_code работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ monitoring_fuel/get_month_by_code не работает: {result.message}")
def test_monitoring_fuel_get_series_by_id_and_columns_with_local_data(self, upload_file):
"""Тест monitoring_fuel get_series_by_id_and_columns с данными из локального storage"""
# Сначала загружаем данные
file_path = upload_file("monitoring.zip")
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='monitoring_fuel',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
upload_result = self.report_service.upload_report(request)
assert upload_result.success is True, f"Загрузка не удалась: {upload_result.message}"
# Теперь тестируем геттер
data_request = DataRequest(
report_type='monitoring_fuel',
get_params={
'mode': 'series_by_id_and_columns',
'columns': ['total', 'normativ']
}
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"✅ monitoring_fuel/get_series_by_id_and_columns работает с локальными данными")
print(f" Получено данных: {len(result.data) if isinstance(result.data, list) else 'не список'}")
else:
print(f"❌ monitoring_fuel/get_series_by_id_and_columns не работает: {result.message}")
def test_all_getters_with_loaded_data(self, upload_file):
"""Тест всех геттеров с предварительно загруженными данными"""
# Загружаем все данные
files_to_upload = [
("svodka_ca.xlsx", "svodka_ca", "file"),
("pm_plan.zip", "svodka_pm", "zip"),
("monitoring.zip", "monitoring_fuel", "zip")
]
for filename, report_type, upload_type in files_to_upload:
file_path = upload_file(filename)
# Читаем файл и создаем UploadRequest
with open(file_path, 'rb') as f:
file_content = f.read()
upload_request = UploadRequest(
report_type=report_type,
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
result = self.report_service.upload_report(upload_request)
assert result.success is True, f"Загрузка {filename} не удалась: {result.message}"
print(f"{filename} загружен")
# Тестируем все геттеры
test_cases = [
# svodka_pm
{
'report_type': 'svodka_pm',
'mode': 'single_og',
'params': {'id': 'SNPZ', 'codes': [78, 79], 'columns': ['ПП', 'СЭБ']},
'name': 'svodka_pm/single_og'
},
{
'report_type': 'svodka_pm',
'mode': 'total_ogs',
'params': {'codes': [78, 79, 394, 395, 396, 397, 81, 82, 83, 84], 'columns': ['БП', 'ПП', 'СЭБ']},
'name': 'svodka_pm/total_ogs'
},
# svodka_ca
{
'report_type': 'svodka_ca',
'mode': 'get_ca_data',
'params': {'modes': ['fact', 'plan'], 'tables': ['table1', 'table2']},
'name': 'svodka_ca/get_ca_data'
},
# monitoring_fuel
{
'report_type': 'monitoring_fuel',
'mode': 'total_by_columns',
'params': {'columns': ['total', 'normativ']},
'name': 'monitoring_fuel/get_total_by_columns'
},
{
'report_type': 'monitoring_fuel',
'mode': 'month_by_code',
'params': {'month': '02'},
'name': 'monitoring_fuel/get_month_by_code'
},
{
'report_type': 'monitoring_fuel',
'mode': 'series_by_id_and_columns',
'params': {'columns': ['total', 'normativ']},
'name': 'monitoring_fuel/get_series_by_id_and_columns'
}
]
print("\n🧪 Тестирование всех геттеров с локальными данными:")
for test_case in test_cases:
request_params = test_case['params'].copy()
request_params['mode'] = test_case['mode']
data_request = DataRequest(
report_type=test_case['report_type'],
get_params=request_params
)
result = self.report_service.get_data(data_request)
if result.success:
print(f"{test_case['name']}: работает")
else:
print(f"{test_case['name']}: {result.message}")
# Показываем содержимое storage
objects = self.storage.list_objects()
print(f"\n📊 Объекты в локальном storage: {len(objects)}")
for obj_id in objects:
metadata = self.storage.get_object_metadata(obj_id)
if metadata:
print(f" 📁 {obj_id}: {metadata['shape']} колонки: {metadata['columns'][:3]}...")

View File

@@ -0,0 +1,102 @@
"""
Тесты для monitoring_fuel эндпоинтов
"""
import pytest
import requests
class TestMonitoringFuelEndpoints:
"""Тесты эндпоинтов monitoring_fuel"""
def test_monitoring_fuel_get_total_by_columns(self, wait_for_api, api_base_url):
"""Тест получения данных по колонкам и расчёт средних значений"""
# Пример из схемы MonitoringFuelTotalRequest
data = {
"columns": ["total", "normativ"]
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_total_by_columns", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_total_by_columns работает: получены данные для колонок {data['columns']}")
def test_monitoring_fuel_get_month_by_code(self, wait_for_api, api_base_url):
"""Тест получения данных за месяц"""
# Пример из схемы MonitoringFuelMonthRequest
data = {
"month": "02"
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_month_by_code", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_month_by_code работает: получены данные за месяц {data['month']}")
def test_monitoring_fuel_get_series_by_id_and_columns(self, wait_for_api, api_base_url):
"""Тест получения временных рядов по ID и колонкам"""
# Пример из схемы MonitoringFuelSeriesRequest
data = {
"columns": ["total", "normativ"]
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_series_by_id_and_columns", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_series_by_id_and_columns работает: получены временные ряды для колонок {data['columns']}")
def test_monitoring_fuel_get_total_by_columns_single_column(self, wait_for_api, api_base_url):
"""Тест получения данных по одной колонке"""
data = {
"columns": ["total"]
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_total_by_columns", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_total_by_columns с одной колонкой работает: получены данные для колонки {data['columns'][0]}")
def test_monitoring_fuel_get_month_by_code_different_month(self, wait_for_api, api_base_url):
"""Тест получения данных за другой месяц"""
data = {
"month": "01"
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_month_by_code", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_month_by_code с другим месяцем работает: получены данные за месяц {data['month']}")
def test_monitoring_fuel_get_series_by_id_and_columns_single_column(self, wait_for_api, api_base_url):
"""Тест получения временных рядов по одной колонке"""
data = {
"columns": ["total"]
}
response = requests.post(f"{api_base_url}/monitoring_fuel/get_series_by_id_and_columns", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ monitoring_fuel/get_series_by_id_and_columns с одной колонкой работает: получены временные ряды для колонки {data['columns'][0]}")

View File

@@ -0,0 +1,134 @@
"""
Прямое тестирование парсеров с локальным storage
Этот модуль тестирует парсеры напрямую, без API
"""
import pytest
import sys
from pathlib import Path
# Добавляем путь к проекту
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "python_parser"))
from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser
from core.services import ReportService
from adapters.local_storage import LocalStorageAdapter
class TestParsersDirect:
"""Прямое тестирование парсеров с локальным storage"""
@pytest.fixture(autouse=True)
def setup_storage(self, clean_storage):
"""Настройка локального storage для каждого теста"""
self.storage = clean_storage
self.report_service = ReportService(self.storage)
def test_svodka_pm_parser_registration(self):
"""Тест регистрации парсера svodka_pm"""
parser = SvodkaPMParser()
getters = parser.get_available_getters()
assert "single_og" in getters
assert "total_ogs" in getters
# Проверяем параметры геттеров
single_og_getter = getters["single_og"]
assert "id" in single_og_getter["required_params"]
assert "codes" in single_og_getter["required_params"]
assert "columns" in single_og_getter["required_params"]
assert "search" in single_og_getter["optional_params"]
total_ogs_getter = getters["total_ogs"]
assert "codes" in total_ogs_getter["required_params"]
assert "columns" in total_ogs_getter["required_params"]
assert "search" in total_ogs_getter["optional_params"]
print("✅ svodka_pm парсер зарегистрирован корректно")
def test_svodka_ca_parser_registration(self):
"""Тест регистрации парсера svodka_ca"""
parser = SvodkaCAParser()
getters = parser.get_available_getters()
assert "get_ca_data" in getters
# Проверяем параметры геттера
getter = getters["get_ca_data"]
assert "modes" in getter["required_params"]
assert "tables" in getter["required_params"]
print("✅ svodka_ca парсер зарегистрирован корректно")
def test_monitoring_fuel_parser_registration(self):
"""Тест регистрации парсера monitoring_fuel"""
parser = MonitoringFuelParser()
getters = parser.get_available_getters()
assert "total_by_columns" in getters
assert "month_by_code" in getters
assert "series_by_id_and_columns" in getters
# Проверяем параметры геттеров
total_getter = getters["total_by_columns"]
assert "columns" in total_getter["required_params"]
month_getter = getters["month_by_code"]
assert "month" in month_getter["required_params"]
series_getter = getters["series_by_id_and_columns"]
assert "columns" in series_getter["required_params"]
print("✅ monitoring_fuel парсер зарегистрирован корректно")
def test_storage_operations(self):
"""Тест операций с локальным storage"""
import pandas as pd
# Создаем тестовый DataFrame
test_df = pd.DataFrame({
'col1': [1, 2, 3],
'col2': ['a', 'b', 'c']
})
# Сохраняем
success = self.storage.save_dataframe("test_object", test_df)
assert success is True
# Проверяем существование
exists = self.storage.object_exists("test_object")
assert exists is True
# Загружаем
loaded_df = self.storage.load_dataframe("test_object")
assert loaded_df is not None
assert loaded_df.shape == (3, 2)
assert list(loaded_df.columns) == ['col1', 'col2']
# Получаем метаданные
metadata = self.storage.get_object_metadata("test_object")
assert metadata is not None
assert metadata["shape"] == [3, 2]
# Получаем список объектов
objects = self.storage.list_objects()
assert "test_object" in objects
# Удаляем
delete_success = self.storage.delete_object("test_object")
assert delete_success is True
# Проверяем, что объект удален
exists_after = self.storage.object_exists("test_object")
assert exists_after is False
print("✅ Локальный storage работает корректно")
def test_report_service_with_local_storage(self):
"""Тест ReportService с локальным storage"""
# Проверяем, что ReportService может работать с локальным storage
assert self.report_service.storage is not None
assert hasattr(self.report_service.storage, 'save_dataframe')
assert hasattr(self.report_service.storage, 'load_dataframe')
print("✅ ReportService корректно работает с локальным storage")

View File

@@ -0,0 +1,58 @@
"""
Тесты для svodka_ca эндпоинтов
"""
import pytest
import requests
class TestSvodkaCAEndpoints:
"""Тесты эндпоинтов svodka_ca"""
def test_svodka_ca_get_ca_data(self, wait_for_api, api_base_url):
"""Тест получения данных из сводок СА"""
# Пример из схемы SvodkaCARequest
data = {
"modes": ["fact", "plan"],
"tables": ["table1", "table2"]
}
response = requests.post(f"{api_base_url}/svodka_ca/get_ca_data", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_ca/get_ca_data работает: получены данные для режимов {data['modes']}")
def test_svodka_ca_get_ca_data_single_mode(self, wait_for_api, api_base_url):
"""Тест получения данных из сводок СА для одного режима"""
data = {
"modes": ["fact"],
"tables": ["table1"]
}
response = requests.post(f"{api_base_url}/svodka_ca/get_ca_data", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_ca/get_ca_data с одним режимом работает: получены данные для режима {data['modes'][0]}")
def test_svodka_ca_get_ca_data_multiple_tables(self, wait_for_api, api_base_url):
"""Тест получения данных из сводок СА для нескольких таблиц"""
data = {
"modes": ["fact", "plan"],
"tables": ["table1", "table2", "table3"]
}
response = requests.post(f"{api_base_url}/svodka_ca/get_ca_data", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_ca/get_ca_data с несколькими таблицами работает: получены данные для {len(data['tables'])} таблиц")

View File

@@ -0,0 +1,79 @@
"""
Тесты для svodka_pm эндпоинтов
"""
import pytest
import requests
class TestSvodkaPMEndpoints:
"""Тесты эндпоинтов svodka_pm"""
def test_svodka_pm_single_og(self, wait_for_api, api_base_url):
"""Тест получения данных по одному ОГ"""
# Пример из схемы SvodkaPMSingleOGRequest
data = {
"id": "SNPZ",
"codes": [78, 79],
"columns": ["ПП", "СЭБ"]
}
response = requests.post(f"{api_base_url}/svodka_pm/single_og", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_pm/single_og работает: получены данные для {data['id']}")
def test_svodka_pm_total_ogs(self, wait_for_api, api_base_url):
"""Тест получения данных по всем ОГ"""
# Пример из схемы SvodkaPMTotalOGsRequest
data = {
"codes": [78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
"columns": ["БП", "ПП", "СЭБ"]
}
response = requests.post(f"{api_base_url}/svodka_pm/get_total_ogs", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_pm/get_total_ogs работает: получены данные по всем ОГ")
def test_svodka_pm_single_og_with_search(self, wait_for_api, api_base_url):
"""Тест получения данных по одному ОГ с параметром search"""
data = {
"id": "SNPZ",
"codes": [78, 79],
"columns": ["ПП", "СЭБ"],
"search": "Итого"
}
response = requests.post(f"{api_base_url}/svodka_pm/single_og", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_pm/single_og с search работает: получены данные для {data['id']} с фильтром")
def test_svodka_pm_total_ogs_with_search(self, wait_for_api, api_base_url):
"""Тест получения данных по всем ОГ с параметром search"""
data = {
"codes": [78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
"columns": ["БП", "ПП", "СЭБ"],
"search": "Итого"
}
response = requests.post(f"{api_base_url}/svodka_pm/get_total_ogs", json=data)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Запрос не удался: {result}"
assert "data" in result, "Отсутствует поле 'data' в ответе"
print(f"✅ svodka_pm/get_total_ogs с search работает: получены данные по всем ОГ с фильтром")

View File

@@ -0,0 +1,52 @@
"""
Тесты для эндпоинтов загрузки данных
"""
import pytest
import requests
from pathlib import Path
class TestUploadEndpoints:
"""Тесты эндпоинтов загрузки"""
def test_upload_svodka_ca(self, wait_for_api, upload_file, api_base_url):
"""Тест загрузки файла svodka_ca.xlsx"""
file_path = upload_file("svodka_ca.xlsx")
with open(file_path, 'rb') as f:
files = {'file': ('svodka_ca.xlsx', f, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
response = requests.post(f"{api_base_url}/svodka_ca/upload", files=files)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Загрузка не удалась: {result}"
print(f"✅ svodka_ca.xlsx загружен успешно: {result['message']}")
def test_upload_svodka_pm_plan(self, wait_for_api, upload_file, api_base_url):
"""Тест загрузки архива pm_plan.zip"""
file_path = upload_file("pm_plan.zip")
with open(file_path, 'rb') as f:
files = {'zip_file': ('pm_plan.zip', f, 'application/zip')}
response = requests.post(f"{api_base_url}/svodka_pm/upload-zip", files=files)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Загрузка не удалась: {result}"
print(f"✅ pm_plan.zip загружен успешно: {result['message']}")
def test_upload_monitoring_fuel(self, wait_for_api, upload_file, api_base_url):
"""Тест загрузки архива monitoring.zip"""
file_path = upload_file("monitoring.zip")
with open(file_path, 'rb') as f:
files = {'zip_file': ('monitoring.zip', f, 'application/zip')}
response = requests.post(f"{api_base_url}/monitoring_fuel/upload-zip", files=files)
assert response.status_code == 200, f"Ожидался статус 200, получен {response.status_code}: {response.text}"
result = response.json()
assert result["success"] is True, f"Загрузка не удалась: {result}"
print(f"✅ monitoring.zip загружен успешно: {result['message']}")

View File

@@ -0,0 +1,183 @@
"""
Тестирование загрузки файлов с сохранением в локальный storage
"""
import pytest
import sys
from pathlib import Path
# Добавляем путь к проекту
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "python_parser"))
from core.services import ReportService, PARSERS
from core.models import UploadRequest
from adapters.local_storage import LocalStorageAdapter
from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser
# Регистрируем парсеры
PARSERS.update({
'svodka_pm': SvodkaPMParser,
'svodka_ca': SvodkaCAParser,
'monitoring_fuel': MonitoringFuelParser,
})
class TestUploadWithLocalStorage:
"""Тестирование загрузки файлов с локальным storage"""
@pytest.fixture(autouse=True)
def setup_storage(self, clean_storage):
"""Настройка локального storage для каждого теста"""
self.storage = clean_storage
self.report_service = ReportService(self.storage)
def test_upload_svodka_ca_to_local_storage(self, upload_file):
"""Тест загрузки svodka_ca.xlsx в локальный storage"""
file_path = upload_file("svodka_ca.xlsx")
# Читаем файл и создаем UploadRequest
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='svodka_ca',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
# Загружаем файл через ReportService
result = self.report_service.upload_report(request)
assert result.success is True, f"Загрузка не удалась: {result.message}"
# Проверяем, что данные сохранились в локальном storage
objects = self.storage.list_objects()
assert len(objects) > 0, "Данные не сохранились в storage"
# Проверяем метаданные
for obj_id in objects:
metadata = self.storage.get_object_metadata(obj_id)
assert metadata is not None, f"Метаданные для {obj_id} не найдены"
assert "shape" in metadata, f"Отсутствует shape в метаданных {obj_id}"
assert "columns" in metadata, f"Отсутствуют columns в метаданных {obj_id}"
print(f"✅ svodka_ca.xlsx загружен в локальный storage: {len(objects)} объектов")
print(f" Объекты: {objects}")
def test_upload_pm_plan_to_local_storage(self, upload_file):
"""Тест загрузки pm_plan.zip в локальный storage"""
file_path = upload_file("pm_plan.zip")
# Читаем файл и создаем UploadRequest
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='svodka_pm',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
# Загружаем архив через ReportService
result = self.report_service.upload_report(request)
assert result.success is True, f"Загрузка не удалась: {result.message}"
# Проверяем, что данные сохранились в локальном storage
objects = self.storage.list_objects()
assert len(objects) > 0, "Данные не сохранились в storage"
# Проверяем метаданные
for obj_id in objects:
metadata = self.storage.get_object_metadata(obj_id)
assert metadata is not None, f"Метаданные для {obj_id} не найдены"
assert "shape" in metadata, f"Отсутствует shape в метаданных {obj_id}"
assert "columns" in metadata, f"Отсутствуют columns в метаданных {obj_id}"
print(f"✅ pm_plan.zip загружен в локальный storage: {len(objects)} объектов")
print(f" Объекты: {objects}")
def test_upload_monitoring_to_local_storage(self, upload_file):
"""Тест загрузки monitoring.zip в локальный storage"""
file_path = upload_file("monitoring.zip")
# Читаем файл и создаем UploadRequest
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type='monitoring_fuel',
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
# Загружаем архив через ReportService
result = self.report_service.upload_report(request)
assert result.success is True, f"Загрузка не удалась: {result.message}"
# Проверяем, что данные сохранились в локальном storage
objects = self.storage.list_objects()
assert len(objects) > 0, "Данные не сохранились в storage"
# Проверяем метаданные
for obj_id in objects:
metadata = self.storage.get_object_metadata(obj_id)
assert metadata is not None, f"Метаданные для {obj_id} не найдены"
assert "shape" in metadata, f"Отсутствует shape в метаданных {obj_id}"
assert "columns" in metadata, f"Отсутствуют columns в метаданных {obj_id}"
print(f"✅ monitoring.zip загружен в локальный storage: {len(objects)} объектов")
print(f" Объекты: {objects}")
def test_upload_all_files_sequence(self, upload_file):
"""Тест последовательной загрузки всех файлов"""
# Загружаем все файлы по очереди
files_to_upload = [
("svodka_ca.xlsx", "svodka_ca", "file"),
("pm_plan.zip", "svodka_pm", "zip"),
("monitoring.zip", "monitoring_fuel", "zip")
]
total_objects = 0
for filename, report_type, upload_type in files_to_upload:
file_path = upload_file(filename)
# Читаем файл и создаем UploadRequest
with open(file_path, 'rb') as f:
file_content = f.read()
request = UploadRequest(
report_type=report_type,
file_name=file_path.name,
file_content=file_content,
parse_params={}
)
result = self.report_service.upload_report(request)
assert result.success is True, f"Загрузка {filename} не удалась: {result.message}"
# Подсчитываем объекты
objects = self.storage.list_objects()
current_count = len(objects)
print(f"{filename} загружен: {current_count - total_objects} новых объектов")
total_objects = current_count
# Проверяем итоговое количество объектов
final_objects = self.storage.list_objects()
assert len(final_objects) > 0, "Ни один файл не был загружен"
print(f"Все файлы загружены. Итого объектов в storage: {len(final_objects)}")
print(f" Все объекты: {final_objects}")
# Выводим детальную информацию о каждом объекте
for obj_id in final_objects:
metadata = self.storage.get_object_metadata(obj_id)
if metadata:
print(f" 📊 {obj_id}: {metadata['shape']} колонки: {metadata['columns'][:5]}...")