"""Tests for core excel parser.""" import re from io import BytesIO from unittest.mock import MagicMock from zipfile import ZIP_DEFLATED, ZipFile from apps.core.excel import ( BaseExcelParser, ColumnMapping, FieldError, ParseResult, RowData, RowValidationError, validate_inn, validate_kpp, validate_ogrn, validate_okpo, ) from django.test import TestCase from openpyxl import Workbook class ValidatorsTest(TestCase): """Tests for validators.""" def test_validate_inn_valid_10_digits(self): self.assertEqual(validate_inn("1234567890"), (True, "")) def test_validate_inn_valid_12_digits(self): self.assertEqual(validate_inn("123456789012"), (True, "")) def test_validate_inn_strips_whitespace(self): self.assertEqual(validate_inn(" 1234567890 "), (True, "")) def test_validate_inn_invalid_length(self): valid, message = validate_inn("12345") self.assertFalse(valid) self.assertIn("10 или 12", message) def test_validate_inn_none_returns_error(self): self.assertEqual(validate_inn(None), (False, "ИНН обязателен")) def test_validate_ogrn_valid_13_digits(self): self.assertEqual(validate_ogrn("1234567890123"), (True, "")) def test_validate_ogrn_valid_15_digits(self): self.assertEqual(validate_ogrn("123456789012345"), (True, "")) def test_validate_ogrn_invalid_length(self): valid, message = validate_ogrn("12345") self.assertFalse(valid) self.assertIn("13 или 15", message) def test_validate_kpp_valid(self): self.assertEqual(validate_kpp("123456789"), (True, "")) def test_validate_kpp_invalid_length(self): valid, message = validate_kpp("12345") self.assertFalse(valid) self.assertIn("9 цифр", message) def test_validate_okpo_valid_8_digits(self): self.assertEqual(validate_okpo("12345678"), (True, "")) def test_validate_okpo_valid_10_digits(self): self.assertEqual(validate_okpo("1234567890"), (True, "")) class DataclassesTest(TestCase): """Tests for dataclasses.""" def test_column_mapping_creation(self): mapping = ColumnMapping( excel_column=1, excel_header="Test Header", model_field="test_field", ) self.assertEqual(mapping.excel_column, 1) self.assertEqual(mapping.field_name, "test_field") self.assertFalse(mapping.required) def test_column_mapping_with_validator(self): mapping = ColumnMapping( excel_column=1, excel_header="ИНН", model_field="inn", required=True, validator=validate_inn, ) self.assertTrue(mapping.required) self.assertIsNotNone(mapping.validator) def test_row_data_creation(self): data = RowData( row_number=5, organization_name="Тестовая организация", inn="1234567890", ogrn="1234567890123", kpp="123456789", okpo="12345678", fields={"field1": "value1", "field2": 123}, ) self.assertEqual(data.row_number, 5) self.assertEqual(data.fields["field1"], "value1") def test_field_error_creation(self): error = FieldError( field="inn", message="Invalid INN", value="12345", ) self.assertEqual(error.field, "inn") self.assertEqual(error.message, "Invalid INN") self.assertEqual(error.value, "12345") def test_row_validation_error_creation(self): error = RowValidationError( row=10, inn="1234567890", kpp="123456789", organization_name="Тест", errors=[FieldError(field="inn", message="Invalid", value="x")], ) self.assertEqual(error.row, 10) self.assertEqual(len(error.errors), 1) def test_parse_result_creation(self): result = ParseResult( batch_id=123, loaded_count=5, skipped_count=1, errors=[], ) self.assertEqual(result.batch_id, 123) self.assertEqual(result.loaded_count, 5) self.assertEqual(result.skipped_count, 1) class BaseExcelParserTest(TestCase): """Tests for BaseExcelParser.""" def test_parser_abstract_methods(self): with self.assertRaises(TypeError): BaseExcelParser() def test_concrete_parser_implementation(self): class TestParser(BaseExcelParser): def get_column_mappings(self): return [ ColumnMapping( excel_column=1, excel_header="ИНН", model_field="inn", required=True, ), ColumnMapping( excel_column=2, excel_header="Наименование", model_field="name", ), ] def get_next_batch_id(self) -> int: return 1 def create_record(self, row_data, batch_id): return MagicMock(id=1, batch_id=batch_id, row_data=row_data) parser = TestParser() mappings = parser.get_column_mappings() self.assertEqual(len(mappings), 2) self.assertEqual(mappings[0].field_name, "inn") def test_parse_recovers_missing_read_only_worksheet_dimension(self): workbook = Workbook() sheet = workbook.active sheet.append(["Организация", "ОКПО", "ОГРН", "ИНН"]) sheet.append( ["Тестовая организация", "12345678", "1234567890123", "1234567890"] ) source = BytesIO() workbook.save(source) workbook.close() source.seek(0) without_dimension = BytesIO() with ZipFile(source, "r") as input_archive, ZipFile( without_dimension, "w", compression=ZIP_DEFLATED, ) as output_archive: for item in input_archive.infolist(): content = input_archive.read(item.filename) if item.filename == "xl/worksheets/sheet1.xml": content = re.sub(rb"]*/>", b"", content, count=1) output_archive.writestr(item, content) without_dimension.seek(0) created_rows = [] class TestParser(BaseExcelParser): def get_column_mappings(self): return [] def get_next_batch_id(self) -> int: return 1 def create_record(self, row_data, batch_id): created_rows.append((row_data, batch_id)) return MagicMock() result = TestParser().parse(without_dimension) self.assertEqual(result.loaded_count, 1) self.assertEqual(result.skipped_count, 0) self.assertEqual(created_rows[0][0].organization_name, "Тестовая организация") self.assertEqual(created_rows[0][1], 1)