no message
This commit is contained in:
2
tests/parsers/__init__.py
Normal file
2
tests/parsers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# Tests for parsers module
|
||||
|
||||
320
tests/parsers/test_excel_parcer.py
Normal file
320
tests/parsers/test_excel_parcer.py
Normal file
@@ -0,0 +1,320 @@
|
||||
import pytest
|
||||
import pandas as pd
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from mail_order_bot.parsers.excel_parcer import ExcelFileParcer
|
||||
|
||||
|
||||
class TestExcelFileParcer:
|
||||
"""Тесты для класса ExcelFileParcer"""
|
||||
|
||||
@pytest.fixture
|
||||
def sample_config(self):
|
||||
"""Базовая конфигурация для тестов"""
|
||||
return {
|
||||
"sheet_name": 0,
|
||||
"key_field": "Артикул",
|
||||
"mapping": {
|
||||
"article": "Артикул",
|
||||
"manufacturer": "Производитель",
|
||||
"name": "Наименование",
|
||||
"price": "Цена",
|
||||
"quantity": "Количество"
|
||||
}
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def sample_excel_bytes(self):
|
||||
"""Создает тестовый Excel файл в виде байтов"""
|
||||
df = pd.DataFrame({
|
||||
'Артикул': ['ART001', 'ART002', 'ART003'],
|
||||
'Производитель': ['MAN001', 'MAN002', 'MAN003'],
|
||||
'Наименование': ['Товар 1', 'Товар 2', 'Товар 3'],
|
||||
'Цена': [100.0, 200.0, 300.0],
|
||||
'Количество': [1, 2, 3]
|
||||
})
|
||||
buf = BytesIO()
|
||||
with pd.ExcelWriter(buf, engine='xlsxwriter') as writer:
|
||||
df.to_excel(writer, sheet_name='Sheet1', index=False)
|
||||
buf.seek(0)
|
||||
return buf.getvalue()
|
||||
|
||||
@pytest.fixture
|
||||
def excel_with_header_row(self):
|
||||
"""Создает Excel файл с заголовком не в первой строке"""
|
||||
df = pd.DataFrame([
|
||||
['Заголовок документа', None, None, None, None],
|
||||
['Артикул', 'Производитель', 'Наименование', 'Цена', 'Количество'],
|
||||
['ART001', 'MAN001', 'Товар 1', 100.0, 1],
|
||||
['ART002', 'MAN002', 'Товар 2', 200.0, 2],
|
||||
['ART003', 'MAN003', 'Товар 3', 300.0, 3],
|
||||
[None, None, None, None, None] # Пустая строка для обрезки
|
||||
])
|
||||
buf = BytesIO()
|
||||
with pd.ExcelWriter(buf, engine='xlsxwriter') as writer:
|
||||
df.to_excel(writer, sheet_name='Sheet1', index=False, header=False)
|
||||
buf.seek(0)
|
||||
return buf.getvalue()
|
||||
|
||||
def test_init_with_valid_file(self, sample_excel_bytes, sample_config):
|
||||
"""Тест инициализации с валидным файлом"""
|
||||
parser = ExcelFileParcer(sample_excel_bytes, sample_config)
|
||||
|
||||
assert parser.config == sample_config
|
||||
assert parser.bytes == sample_excel_bytes
|
||||
assert parser.sheet_name == 0
|
||||
assert parser.df is not None
|
||||
assert isinstance(parser.df, pd.DataFrame)
|
||||
|
||||
def test_init_with_custom_sheet_name(self, sample_excel_bytes):
|
||||
"""Тест инициализации с кастомным именем листа"""
|
||||
config = {
|
||||
"sheet_name": "Sheet2",
|
||||
"key_field": "Артикул",
|
||||
"mapping": {
|
||||
"article": "Артикул",
|
||||
"manufacturer": "Производитель"
|
||||
}
|
||||
}
|
||||
parser = ExcelFileParcer(sample_excel_bytes, config)
|
||||
assert parser.sheet_name == "Sheet2"
|
||||
|
||||
def test_init_with_default_sheet_name(self, sample_excel_bytes):
|
||||
"""Тест инициализации с дефолтным именем листа"""
|
||||
config = {
|
||||
"key_field": "Артикул",
|
||||
"mapping": {
|
||||
"article": "Артикул",
|
||||
"manufacturer": "Производитель"
|
||||
}
|
||||
}
|
||||
parser = ExcelFileParcer(sample_excel_bytes, config)
|
||||
assert parser.sheet_name == 0
|
||||
|
||||
@patch('mail_order_bot.parsers.excel_parcer.pd.read_excel')
|
||||
def test_init_with_invalid_file(self, mock_read_excel, sample_config):
|
||||
"""Тест инициализации с невалидным файлом"""
|
||||
mock_read_excel.side_effect = Exception("Ошибка парсинга")
|
||||
invalid_bytes = b"invalid excel content"
|
||||
|
||||
parser = ExcelFileParcer(invalid_bytes, sample_config)
|
||||
|
||||
assert parser.df is None
|
||||
|
||||
def test_parse_file_success(self, sample_excel_bytes, sample_config):
|
||||
"""Тест успешного парсинга файла"""
|
||||
parser = ExcelFileParcer(sample_excel_bytes, sample_config)
|
||||
assert parser.df is not None
|
||||
assert len(parser.df) > 0
|
||||
|
||||
@patch('mail_order_bot.parsers.excel_parcer.pd.read_excel')
|
||||
def test_parse_file_failure(self, mock_read_excel, sample_config):
|
||||
"""Тест обработки ошибки при парсинге файла"""
|
||||
mock_read_excel.side_effect = Exception("Ошибка чтения")
|
||||
invalid_bytes = b"invalid"
|
||||
|
||||
parser = ExcelFileParcer(invalid_bytes, sample_config)
|
||||
assert parser.df is None
|
||||
|
||||
def test_get_header_row(self, excel_with_header_row, sample_config):
|
||||
"""Тест поиска строки заголовка"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
header_row = parser._get_header_row()
|
||||
|
||||
assert header_row == 1 # Заголовок во второй строке (индекс 1)
|
||||
|
||||
def test_get_attr_column(self, excel_with_header_row, sample_config):
|
||||
"""Тест поиска индекса колонки по имени"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
col_idx = parser._get_attr_column("Артикул")
|
||||
|
||||
assert isinstance(col_idx, int)
|
||||
assert col_idx >= 0
|
||||
|
||||
def test_get_attr_column_nonexistent(self, excel_with_header_row, sample_config):
|
||||
"""Тест поиска несуществующей колонки"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
|
||||
with pytest.raises((IndexError, KeyError)):
|
||||
parser._get_attr_column("Несуществующая колонка")
|
||||
|
||||
def test_get_attr_row(self, excel_with_header_row, sample_config):
|
||||
"""Тест поиска строки по артикулу и производителю"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
row_idx = parser._get_attr_row("ART001", "MAN001")
|
||||
|
||||
assert isinstance(row_idx, (int, pd.core.indexes.numeric.Int64Index))
|
||||
# Проверяем, что индекс найден
|
||||
assert row_idx is not None
|
||||
|
||||
def test_get_attr_row_nonexistent(self, excel_with_header_row, sample_config):
|
||||
"""Тест поиска несуществующей строки"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
|
||||
with pytest.raises((IndexError, KeyError)):
|
||||
parser._get_attr_row("NONEXISTENT", "NONEXISTENT")
|
||||
|
||||
def test_set_value(self, excel_with_header_row, sample_config):
|
||||
"""Тест установки значения в ячейку"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
|
||||
# Получаем исходное значение
|
||||
original_value = parser.df.iloc[2, 3] # Строка с ART001, колонка "Цена"
|
||||
|
||||
# Устанавливаем новое значение
|
||||
parser.set_value("ART001", "MAN001", "Цена", 999.0)
|
||||
|
||||
# Проверяем, что значение изменилось
|
||||
new_value = parser.df.iloc[2, 3]
|
||||
assert new_value == 999.0
|
||||
assert new_value != original_value
|
||||
|
||||
def test_get_file_bytes(self, sample_excel_bytes, sample_config):
|
||||
"""Тест получения файла в виде байтов"""
|
||||
parser = ExcelFileParcer(sample_excel_bytes, sample_config)
|
||||
result_bytes = parser.get_file_bytes()
|
||||
|
||||
assert result_bytes is not None
|
||||
assert hasattr(result_bytes, 'read')
|
||||
assert hasattr(result_bytes, 'seek')
|
||||
|
||||
# Проверяем, что можно прочитать байты
|
||||
result_bytes.seek(0)
|
||||
content = result_bytes.read()
|
||||
assert len(content) > 0
|
||||
|
||||
def test_get_file_bytes_creates_valid_excel(self, sample_excel_bytes, sample_config):
|
||||
"""Тест что get_file_bytes создает валидный Excel файл"""
|
||||
parser = ExcelFileParcer(sample_excel_bytes, sample_config)
|
||||
result_bytes = parser.get_file_bytes()
|
||||
|
||||
# Пытаемся прочитать созданный файл
|
||||
result_bytes.seek(0)
|
||||
df = pd.read_excel(result_bytes, sheet_name=0, header=None)
|
||||
|
||||
assert df is not None
|
||||
assert len(df) > 0
|
||||
|
||||
def test_get_order_rows(self, excel_with_header_row, sample_config):
|
||||
"""Тест получения строк заказа"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
order_rows = parser.get_order_rows()
|
||||
|
||||
assert order_rows is not None
|
||||
assert isinstance(order_rows, pd.DataFrame)
|
||||
assert len(order_rows) > 0
|
||||
# Проверяем, что пустая строка обрезана
|
||||
assert len(order_rows) == 3 # Только строки с данными
|
||||
|
||||
def test_get_order_rows_with_empty_file(self, sample_config):
|
||||
"""Тест получения строк заказа из пустого файла"""
|
||||
# Создаем пустой DataFrame
|
||||
df = pd.DataFrame([['Артикул', 'Производитель'], [None, None]])
|
||||
buf = BytesIO()
|
||||
with pd.ExcelWriter(buf, engine='xlsxwriter') as writer:
|
||||
df.to_excel(writer, sheet_name='Sheet1', index=False, header=False)
|
||||
buf.seek(0)
|
||||
empty_bytes = buf.getvalue()
|
||||
|
||||
parser = ExcelFileParcer(empty_bytes, sample_config)
|
||||
|
||||
# Должен вернуть пустой DataFrame или вызвать ошибку
|
||||
try:
|
||||
order_rows = parser.get_order_rows()
|
||||
assert len(order_rows) == 0
|
||||
except (IndexError, KeyError):
|
||||
# Ожидаемое поведение при отсутствии данных
|
||||
pass
|
||||
|
||||
def test_set_value_updates_dataframe(self, excel_with_header_row, sample_config):
|
||||
"""Тест что set_value обновляет DataFrame"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
|
||||
# Находим строку с ART002
|
||||
row_idx = parser._get_attr_row("ART002", "MAN002")
|
||||
price_col_idx = parser._get_attr_column("Цена")
|
||||
|
||||
original_price = parser.df.iloc[row_idx, price_col_idx]
|
||||
|
||||
# Устанавливаем новое значение
|
||||
parser.set_value("ART002", "MAN002", "Цена", 555.0)
|
||||
|
||||
# Проверяем обновление
|
||||
updated_price = parser.df.iloc[row_idx, price_col_idx]
|
||||
assert updated_price == 555.0
|
||||
assert updated_price != original_price
|
||||
|
||||
def test_multiple_set_value_operations(self, excel_with_header_row, sample_config):
|
||||
"""Тест множественных операций set_value"""
|
||||
parser = ExcelFileParcer(excel_with_header_row, sample_config)
|
||||
|
||||
# Устанавливаем несколько значений
|
||||
parser.set_value("ART001", "MAN001", "Цена", 111.0)
|
||||
parser.set_value("ART002", "MAN002", "Цена", 222.0)
|
||||
parser.set_value("ART003", "MAN003", "Цена", 333.0)
|
||||
|
||||
# Проверяем все значения
|
||||
price_col_idx = parser._get_attr_column("Цена")
|
||||
row1_idx = parser._get_attr_row("ART001", "MAN001")
|
||||
row2_idx = parser._get_attr_row("ART002", "MAN002")
|
||||
row3_idx = parser._get_attr_row("ART003", "MAN003")
|
||||
|
||||
assert parser.df.iloc[row1_idx, price_col_idx] == 111.0
|
||||
assert parser.df.iloc[row2_idx, price_col_idx] == 222.0
|
||||
assert parser.df.iloc[row3_idx, price_col_idx] == 333.0
|
||||
|
||||
def test_get_order_rows_trimmed_correctly(self, sample_config):
|
||||
"""Тест что get_order_rows правильно обрезает пустые строки"""
|
||||
# Создаем файл с пустой строкой в середине
|
||||
df = pd.DataFrame([
|
||||
['Артикул', 'Производитель', 'Наименование'],
|
||||
['ART001', 'MAN001', 'Товар 1'],
|
||||
['ART002', 'MAN002', 'Товар 2'],
|
||||
[None, None, None], # Пустая строка
|
||||
['ART003', 'MAN003', 'Товар 3'],
|
||||
[None, None, None] # Еще одна пустая строка
|
||||
])
|
||||
buf = BytesIO()
|
||||
with pd.ExcelWriter(buf, engine='xlsxwriter') as writer:
|
||||
df.to_excel(writer, sheet_name='Sheet1', index=False, header=False)
|
||||
buf.seek(0)
|
||||
excel_bytes = buf.getvalue()
|
||||
|
||||
parser = ExcelFileParcer(excel_bytes, sample_config)
|
||||
order_rows = parser.get_order_rows()
|
||||
|
||||
# Должны остаться только строки до первой пустой
|
||||
assert len(order_rows) == 2 # ART001 и ART002
|
||||
|
||||
@patch('mail_order_bot.parsers.excel_parcer.pd.read_excel')
|
||||
def test_get_order_rows_with_calamine_engine(self, mock_read_excel, sample_config):
|
||||
"""Тест что get_order_rows использует calamine engine"""
|
||||
# Создаем мок DataFrame
|
||||
mock_df = pd.DataFrame({
|
||||
'Артикул': ['ART001', 'ART002', None],
|
||||
'Производитель': ['MAN001', 'MAN002', None]
|
||||
})
|
||||
mock_read_excel.return_value = mock_df
|
||||
|
||||
# Создаем парсер с моком для первого чтения
|
||||
df_init = pd.DataFrame([
|
||||
['Артикул', 'Производитель'],
|
||||
['ART001', 'MAN001'],
|
||||
['ART002', 'MAN002'],
|
||||
[None, None]
|
||||
])
|
||||
with patch('mail_order_bot.parsers.excel_parcer.pd.read_excel') as mock_init:
|
||||
mock_init.return_value = df_init
|
||||
parser = ExcelFileParcer(b"test", sample_config)
|
||||
|
||||
# Тестируем get_order_rows
|
||||
with patch('mail_order_bot.parsers.excel_parcer.pd.read_excel') as mock_get:
|
||||
mock_get.return_value = mock_df
|
||||
result = parser.get_order_rows()
|
||||
|
||||
# Проверяем, что был вызван read_excel с engine='calamine'
|
||||
mock_get.assert_called_once()
|
||||
call_kwargs = mock_get.call_args[1]
|
||||
assert call_kwargs.get('engine') == 'calamine'
|
||||
|
||||
Reference in New Issue
Block a user