first commit - MVE Incrementables Parser microservice with FastAPI, JWT, Celery, Redis

This commit is contained in:
Ernesto Herrera
2026-03-02 21:31:50 -07:00
commit 068d859f42
27 changed files with 2337 additions and 0 deletions

201
tests/test_parser.py Normal file
View File

@@ -0,0 +1,201 @@
"""Tests for the incrementables parser service."""
import pytest
from app.services.parser import IncrementablesParser, ParsingError, parse_incrementables
# Sample text fixtures
SAMPLE_TEXT_COMPLETE = """
CARTA INSTRUCTIVO DE EMBARQUE No. 228718
AJUSTE DE INCREMENTABLES EN:
Fletes: $1,591.20 USD
Seguros: $250.50 USD
Almacenaje/Consolidación: $100.00 USD
Regalías: $75.00 USD
Total: $2,016.70 USD
"""
SAMPLE_TEXT_EMPTY_FIELDS = """
AJUSTE DE INCREMENTABLES EN:
Fletes: $1,591.20 USD
Seguros: USD
Almacenaje/Consolidación: $0.00 USD
Regalías: USD
Observaciones: Los seguros y regalías están vacíos
"""
SAMPLE_TEXT_NO_ANCHOR = """
Este es un documento sin la sección de incrementables.
Solo tiene texto normal sin el ancla esperada.
"""
SAMPLE_TEXT_VARIATIONS = """
INCREMENTABLES EN:
Fletes: $2,500.00 USD
Seguros: USD
Almacenaje Consolidacion: $150.75 USD
Regalias: $300.00 USD
"""
class TestIncrementablesParser:
"""Test cases for IncrementablesParser class."""
def test_parse_complete_data(self):
"""Test parsing with all fields present."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
result = parser.parse()
assert result["currency"] == "USD"
assert result["fletes"] == 1591.20
assert result["seguros"] == 250.50
assert result["almacenaje_consolidacion"] == 100.00
assert result["regalias"] == 75.00
assert len(result["anchors_found"]) > 0
def test_parse_empty_fields(self):
"""Test parsing with empty seguros and regalias."""
parser = IncrementablesParser(SAMPLE_TEXT_EMPTY_FIELDS)
result = parser.parse()
assert result["currency"] == "USD"
assert result["fletes"] == 1591.20
assert result["seguros"] is None
assert result["almacenaje_consolidacion"] == 0.00
assert result["regalias"] is None
def test_parse_no_anchor(self):
"""Test parsing fails when anchor not found."""
parser = IncrementablesParser(SAMPLE_TEXT_NO_ANCHOR)
with pytest.raises(ParsingError) as exc_info:
parser.parse()
assert "not found" in str(exc_info.value).lower()
def test_parse_variations(self):
"""Test parsing with text variations."""
parser = IncrementablesParser(SAMPLE_TEXT_VARIATIONS)
result = parser.parse()
assert result["currency"] == "USD"
assert result["fletes"] == 2500.00
assert result["seguros"] is None
assert result["almacenaje_consolidacion"] == 150.75
assert result["regalias"] == 300.00
def test_parse_amount_with_commas(self):
"""Test amount parsing with comma separators."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
amount = parser._parse_amount("1,591.20")
assert amount == 1591.20
def test_parse_amount_with_dollar(self):
"""Test amount parsing with dollar sign."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
amount = parser._parse_amount("$1,591.20")
assert amount == 1591.20
def test_parse_amount_empty(self):
"""Test amount parsing with empty string."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
amount = parser._parse_amount("")
assert amount is None
def test_parse_amount_none(self):
"""Test amount parsing with None."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
amount = parser._parse_amount(None)
assert amount is None
def test_extract_currency_default(self):
"""Test currency extraction defaults to USD."""
parser = IncrementablesParser("Text without currency")
currency = parser._extract_currency("No currency here")
assert currency == "USD"
assert len(parser.warnings) > 0
assert "currency" in parser.warnings[0].lower()
def test_find_incrementables_section(self):
"""Test finding incrementables section."""
parser = IncrementablesParser(SAMPLE_TEXT_COMPLETE)
section = parser._find_incrementables_section()
assert section is not None
assert "Fletes" in section
assert len(parser.anchors_found) > 0
def test_parse_incrementables_function():
"""Test the main parse_incrementables function."""
result = parse_incrementables(SAMPLE_TEXT_COMPLETE)
assert result["currency"] == "USD"
assert result["fletes"] == 1591.20
assert "anchors_found" in result
assert "warnings" in result
def test_parse_incrementables_raises_error():
"""Test parse_incrementables raises error on invalid input."""
with pytest.raises(ParsingError):
parse_incrementables(SAMPLE_TEXT_NO_ANCHOR)
class TestParserEdgeCases:
"""Test edge cases and error conditions."""
def test_missing_fletes(self):
"""Test that missing fletes raises error."""
text = """
AJUSTE DE INCREMENTABLES EN:
Seguros: $100.00 USD
Almacenaje: $50.00 USD
"""
parser = IncrementablesParser(text)
with pytest.raises(ParsingError) as exc_info:
parser.parse()
assert "fletes" in str(exc_info.value).lower()
def test_missing_almacenaje(self):
"""Test that missing almacenaje raises error."""
text = """
AJUSTE DE INCREMENTABLES EN:
Fletes: $1000.00 USD
Seguros: $100.00 USD
"""
parser = IncrementablesParser(text)
with pytest.raises(ParsingError) as exc_info:
parser.parse()
assert "almacenaje" in str(exc_info.value).lower()
def test_case_insensitive_anchor(self):
"""Test anchor detection is case insensitive."""
text_lower = "ajuste de incrementables en:\nFletes: $100.00 USD\nAlmacenaje: $50.00 USD"
parser = IncrementablesParser(text_lower)
section = parser._find_incrementables_section()
assert section is not None
def test_accent_variations_regalias(self):
"""Test that both 'regalías' and 'regalias' work."""
text_with_accent = """
AJUSTE DE INCREMENTABLES EN:
Fletes: $100.00 USD
Almacenaje: $50.00 USD
Regalías: $25.00 USD
"""
parser = IncrementablesParser(text_with_accent)
result = parser.parse()
assert result["regalias"] == 25.00