Files
mail_order_bot/tests/parsers/test_excel_parcer.py
2026-01-06 21:25:45 +03:00

321 lines
14 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.

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'