Initial commit - production deployment
This commit is contained in:
384
services/sales/tests/unit/test_data_import.py
Normal file
384
services/sales/tests/unit/test_data_import.py
Normal file
@@ -0,0 +1,384 @@
|
||||
# services/sales/tests/unit/test_data_import.py
|
||||
"""
|
||||
Unit tests for Data Import Service
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import base64
|
||||
from decimal import Decimal
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from app.services.data_import_service import DataImportService, SalesValidationResult, SalesImportResult
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestDataImportService:
|
||||
"""Test Data Import Service functionality"""
|
||||
|
||||
@pytest.fixture
|
||||
def import_service(self):
|
||||
"""Create data import service instance"""
|
||||
return DataImportService()
|
||||
|
||||
async def test_validate_csv_import_data_valid(self, import_service, sample_tenant_id, sample_csv_data):
|
||||
"""Test validation of valid CSV import data"""
|
||||
data = {
|
||||
"tenant_id": str(sample_tenant_id),
|
||||
"data": sample_csv_data,
|
||||
"data_format": "csv"
|
||||
}
|
||||
|
||||
result = await import_service.validate_import_data(data)
|
||||
|
||||
assert result.is_valid is True
|
||||
assert result.total_records == 5
|
||||
assert len(result.errors) == 0
|
||||
assert result.summary["status"] == "valid"
|
||||
|
||||
async def test_validate_csv_import_data_missing_tenant(self, import_service, sample_csv_data):
|
||||
"""Test validation with missing tenant_id"""
|
||||
data = {
|
||||
"data": sample_csv_data,
|
||||
"data_format": "csv"
|
||||
}
|
||||
|
||||
result = await import_service.validate_import_data(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any(error["code"] == "MISSING_TENANT_ID" for error in result.errors)
|
||||
|
||||
async def test_validate_csv_import_data_empty_file(self, import_service, sample_tenant_id):
|
||||
"""Test validation with empty file"""
|
||||
data = {
|
||||
"tenant_id": str(sample_tenant_id),
|
||||
"data": "",
|
||||
"data_format": "csv"
|
||||
}
|
||||
|
||||
result = await import_service.validate_import_data(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any(error["code"] == "EMPTY_FILE" for error in result.errors)
|
||||
|
||||
async def test_validate_csv_import_data_unsupported_format(self, import_service, sample_tenant_id):
|
||||
"""Test validation with unsupported format"""
|
||||
data = {
|
||||
"tenant_id": str(sample_tenant_id),
|
||||
"data": "some data",
|
||||
"data_format": "unsupported"
|
||||
}
|
||||
|
||||
result = await import_service.validate_import_data(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any(error["code"] == "UNSUPPORTED_FORMAT" for error in result.errors)
|
||||
|
||||
async def test_validate_csv_missing_required_columns(self, import_service, sample_tenant_id):
|
||||
"""Test validation with missing required columns"""
|
||||
invalid_csv = "invalid_column,another_invalid\nvalue1,value2"
|
||||
data = {
|
||||
"tenant_id": str(sample_tenant_id),
|
||||
"data": invalid_csv,
|
||||
"data_format": "csv"
|
||||
}
|
||||
|
||||
result = await import_service.validate_import_data(data)
|
||||
|
||||
assert result.is_valid is False
|
||||
assert any(error["code"] == "MISSING_DATE_COLUMN" for error in result.errors)
|
||||
assert any(error["code"] == "MISSING_PRODUCT_COLUMN" for error in result.errors)
|
||||
|
||||
async def test_process_csv_import_success(self, import_service, sample_tenant_id, sample_csv_data):
|
||||
"""Test successful CSV import processing"""
|
||||
with patch('app.services.data_import_service.get_db_transaction') as mock_get_db:
|
||||
mock_db = AsyncMock()
|
||||
mock_get_db.return_value.__aenter__.return_value = mock_db
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
mock_repository.create_sales_record.return_value = AsyncMock()
|
||||
|
||||
with patch('app.services.data_import_service.SalesRepository', return_value=mock_repository):
|
||||
result = await import_service.process_import(
|
||||
sample_tenant_id,
|
||||
sample_csv_data,
|
||||
"csv",
|
||||
"test.csv"
|
||||
)
|
||||
|
||||
assert result.success is True
|
||||
assert result.records_processed == 5
|
||||
assert result.records_created == 5
|
||||
assert result.records_failed == 0
|
||||
|
||||
async def test_process_json_import_success(self, import_service, sample_tenant_id, sample_json_data):
|
||||
"""Test successful JSON import processing"""
|
||||
with patch('app.services.data_import_service.get_db_transaction') as mock_get_db:
|
||||
mock_db = AsyncMock()
|
||||
mock_get_db.return_value.__aenter__.return_value = mock_db
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
mock_repository.create_sales_record.return_value = AsyncMock()
|
||||
|
||||
with patch('app.services.data_import_service.SalesRepository', return_value=mock_repository):
|
||||
result = await import_service.process_import(
|
||||
sample_tenant_id,
|
||||
sample_json_data,
|
||||
"json",
|
||||
"test.json"
|
||||
)
|
||||
|
||||
assert result.success is True
|
||||
assert result.records_processed == 2
|
||||
assert result.records_created == 2
|
||||
|
||||
async def test_process_excel_import_base64(self, import_service, sample_tenant_id):
|
||||
"""Test Excel import with base64 encoded data"""
|
||||
# Create a simple Excel-like data structure
|
||||
excel_data = json.dumps([{
|
||||
"date": "2024-01-15",
|
||||
"product": "Pan Integral",
|
||||
"quantity": 5,
|
||||
"revenue": 12.50
|
||||
}])
|
||||
|
||||
# Encode as base64
|
||||
encoded_data = "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + \
|
||||
base64.b64encode(excel_data.encode()).decode()
|
||||
|
||||
with patch('app.services.data_import_service.get_db_transaction') as mock_get_db:
|
||||
mock_db = AsyncMock()
|
||||
mock_get_db.return_value.__aenter__.return_value = mock_db
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
mock_repository.create_sales_record.return_value = AsyncMock()
|
||||
|
||||
# Mock pandas.read_excel to avoid dependency issues
|
||||
with patch('pandas.read_excel') as mock_read_excel:
|
||||
import pandas as pd
|
||||
mock_df = pd.DataFrame([{
|
||||
"date": "2024-01-15",
|
||||
"product": "Pan Integral",
|
||||
"quantity": 5,
|
||||
"revenue": 12.50
|
||||
}])
|
||||
mock_read_excel.return_value = mock_df
|
||||
|
||||
with patch('app.services.data_import_service.SalesRepository', return_value=mock_repository):
|
||||
result = await import_service.process_import(
|
||||
sample_tenant_id,
|
||||
encoded_data,
|
||||
"excel",
|
||||
"test.xlsx"
|
||||
)
|
||||
|
||||
assert result.success is True
|
||||
assert result.records_created == 1
|
||||
|
||||
async def test_detect_columns_mapping(self, import_service):
|
||||
"""Test column detection and mapping"""
|
||||
columns = ["fecha", "producto", "cantidad", "ingresos", "tienda"]
|
||||
|
||||
mapping = import_service._detect_columns(columns)
|
||||
|
||||
assert mapping["date"] == "fecha"
|
||||
assert mapping["product"] == "producto"
|
||||
assert mapping["quantity"] == "cantidad"
|
||||
assert mapping["revenue"] == "ingresos"
|
||||
assert mapping["location"] == "tienda"
|
||||
|
||||
async def test_parse_date_multiple_formats(self, import_service):
|
||||
"""Test date parsing with different formats"""
|
||||
# Test various date formats
|
||||
dates_to_test = [
|
||||
"2024-01-15",
|
||||
"15/01/2024",
|
||||
"01/15/2024",
|
||||
"15-01-2024",
|
||||
"2024/01/15",
|
||||
"2024-01-15 10:30:00"
|
||||
]
|
||||
|
||||
for date_str in dates_to_test:
|
||||
result = import_service._parse_date(date_str)
|
||||
assert result is not None
|
||||
assert isinstance(result, datetime)
|
||||
|
||||
async def test_parse_date_invalid_formats(self, import_service):
|
||||
"""Test date parsing with invalid formats"""
|
||||
invalid_dates = ["invalid", "not-a-date", "", None, "32/13/2024"]
|
||||
|
||||
for date_str in invalid_dates:
|
||||
result = import_service._parse_date(date_str)
|
||||
assert result is None
|
||||
|
||||
async def test_clean_product_name(self, import_service):
|
||||
"""Test product name cleaning"""
|
||||
test_cases = [
|
||||
(" pan de molde ", "Pan De Molde"),
|
||||
("café con leche!!!", "Café Con Leche"),
|
||||
("té verde orgánico", "Té Verde Orgánico"),
|
||||
("bocadillo de jamón", "Bocadillo De Jamón"),
|
||||
("", "Producto sin nombre"),
|
||||
(None, "Producto sin nombre")
|
||||
]
|
||||
|
||||
for input_name, expected in test_cases:
|
||||
result = import_service._clean_product_name(input_name)
|
||||
assert result == expected
|
||||
|
||||
async def test_parse_row_data_valid(self, import_service):
|
||||
"""Test parsing valid row data"""
|
||||
row = {
|
||||
"fecha": "2024-01-15",
|
||||
"producto": "Pan Integral",
|
||||
"cantidad": "5",
|
||||
"ingresos": "12.50",
|
||||
"tienda": "STORE_001"
|
||||
}
|
||||
|
||||
column_mapping = {
|
||||
"date": "fecha",
|
||||
"product": "producto",
|
||||
"quantity": "cantidad",
|
||||
"revenue": "ingresos",
|
||||
"location": "tienda"
|
||||
}
|
||||
|
||||
result = await import_service._parse_row_data(row, column_mapping, 1)
|
||||
|
||||
assert result["skip"] is False
|
||||
assert result["product_name"] == "Pan Integral"
|
||||
assert "inventory_product_id" in result # Should be generated during parsing
|
||||
assert result["quantity_sold"] == 5
|
||||
assert result["revenue"] == 12.5
|
||||
assert result["location_id"] == "STORE_001"
|
||||
|
||||
async def test_parse_row_data_missing_required(self, import_service):
|
||||
"""Test parsing row data with missing required fields"""
|
||||
row = {
|
||||
"producto": "Pan Integral",
|
||||
"cantidad": "5"
|
||||
# Missing date
|
||||
}
|
||||
|
||||
column_mapping = {
|
||||
"date": "fecha",
|
||||
"product": "producto",
|
||||
"quantity": "cantidad"
|
||||
}
|
||||
|
||||
result = await import_service._parse_row_data(row, column_mapping, 1)
|
||||
|
||||
assert result["skip"] is True
|
||||
assert len(result["errors"]) > 0
|
||||
assert "Missing date" in result["errors"][0]
|
||||
|
||||
async def test_parse_row_data_invalid_quantity(self, import_service):
|
||||
"""Test parsing row data with invalid quantity"""
|
||||
row = {
|
||||
"fecha": "2024-01-15",
|
||||
"producto": "Pan Integral",
|
||||
"cantidad": "invalid_quantity"
|
||||
}
|
||||
|
||||
column_mapping = {
|
||||
"date": "fecha",
|
||||
"product": "producto",
|
||||
"quantity": "cantidad"
|
||||
}
|
||||
|
||||
result = await import_service._parse_row_data(row, column_mapping, 1)
|
||||
|
||||
assert result["skip"] is False # Should not skip, just use default
|
||||
assert result["quantity_sold"] == 1 # Default quantity
|
||||
assert len(result["warnings"]) > 0
|
||||
|
||||
async def test_structure_messages(self, import_service):
|
||||
"""Test message structuring"""
|
||||
messages = [
|
||||
"Simple string message",
|
||||
{
|
||||
"type": "existing_dict",
|
||||
"message": "Already structured",
|
||||
"code": "TEST_CODE"
|
||||
}
|
||||
]
|
||||
|
||||
result = import_service._structure_messages(messages)
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0]["type"] == "general_message"
|
||||
assert result[0]["message"] == "Simple string message"
|
||||
assert result[1]["type"] == "existing_dict"
|
||||
|
||||
async def test_generate_suggestions_valid_file(self, import_service):
|
||||
"""Test suggestion generation for valid files"""
|
||||
validation_result = SalesValidationResult(
|
||||
is_valid=True,
|
||||
total_records=50,
|
||||
valid_records=50,
|
||||
invalid_records=0,
|
||||
errors=[],
|
||||
warnings=[],
|
||||
summary={}
|
||||
)
|
||||
|
||||
suggestions = import_service._generate_suggestions(validation_result, "csv", 0)
|
||||
|
||||
assert "El archivo está listo para procesamiento" in suggestions
|
||||
assert "Se procesarán aproximadamente 50 registros" in suggestions
|
||||
|
||||
async def test_generate_suggestions_large_file(self, import_service):
|
||||
"""Test suggestion generation for large files"""
|
||||
validation_result = SalesValidationResult(
|
||||
is_valid=True,
|
||||
total_records=2000,
|
||||
valid_records=2000,
|
||||
invalid_records=0,
|
||||
errors=[],
|
||||
warnings=[],
|
||||
summary={}
|
||||
)
|
||||
|
||||
suggestions = import_service._generate_suggestions(validation_result, "csv", 0)
|
||||
|
||||
assert "Archivo grande: el procesamiento puede tomar varios minutos" in suggestions
|
||||
|
||||
async def test_import_error_handling(self, import_service, sample_tenant_id):
|
||||
"""Test import error handling"""
|
||||
# Test with unsupported format
|
||||
with pytest.raises(ValueError, match="Unsupported format"):
|
||||
await import_service.process_import(
|
||||
sample_tenant_id,
|
||||
"some data",
|
||||
"unsupported_format"
|
||||
)
|
||||
|
||||
async def test_performance_large_import(self, import_service, sample_tenant_id, large_csv_data):
|
||||
"""Test performance with large CSV import"""
|
||||
with patch('app.services.data_import_service.get_db_transaction') as mock_get_db:
|
||||
mock_db = AsyncMock()
|
||||
mock_get_db.return_value.__aenter__.return_value = mock_db
|
||||
|
||||
mock_repository = AsyncMock()
|
||||
mock_repository.create_sales_record.return_value = AsyncMock()
|
||||
|
||||
with patch('app.services.data_import_service.SalesRepository', return_value=mock_repository):
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
result = await import_service.process_import(
|
||||
sample_tenant_id,
|
||||
large_csv_data,
|
||||
"csv",
|
||||
"large_test.csv"
|
||||
)
|
||||
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
|
||||
assert result.success is True
|
||||
assert result.records_processed == 1000
|
||||
assert execution_time < 10.0 # Should complete in under 10 seconds
|
||||
Reference in New Issue
Block a user