"""Tests for Pydantic models.""" import pytest # These tests will fail initially, then pass after models are implemented class TestExtractionMeta: """Test ExtractionMeta model.""" def test_minimal_extraction_meta(self): """Test ExtractionMeta with minimal required fields.""" from src.models import ExtractionMeta meta = ExtractionMeta(pages=1, extraction_time_ms=234) assert meta.pages == 1 assert meta.extraction_time_ms == 234 assert meta.xml_attachment_name is None def test_full_extraction_meta(self): """Test ExtractionMeta with all fields.""" from src.models import ExtractionMeta meta = ExtractionMeta( pages=2, xml_attachment_name="factur-x.xml", extraction_time_ms=456 ) assert meta.pages == 2 assert meta.xml_attachment_name == "factur-x.xml" assert meta.extraction_time_ms == 456 class TestSupplier: """Test Supplier model.""" def test_minimal_supplier(self): """Test Supplier with minimal required fields.""" from src.models import Supplier supplier = Supplier(name="ACME GmbH") assert supplier.name == "ACME GmbH" assert supplier.street is None assert supplier.vat_id is None def test_full_supplier(self): """Test Supplier with all fields.""" from src.models import Supplier supplier = Supplier( name="ACME GmbH", street="Musterstraße 42", postal_code="12345", city="Musterstadt", country="DE", vat_id="DE123456789", email="info@acme.de", ) assert supplier.name == "ACME GmbH" assert supplier.street == "Musterstraße 42" assert supplier.postal_code == "12345" assert supplier.city == "Musterstadt" assert supplier.country == "DE" assert supplier.vat_id == "DE123456789" assert supplier.email == "info@acme.de" class TestBuyer: """Test Buyer model.""" def test_minimal_buyer(self): """Test Buyer with minimal required fields.""" from src.models import Buyer buyer = Buyer(name="Customer AG") assert buyer.name == "Customer AG" assert buyer.street is None def test_full_buyer(self): """Test Buyer with all fields.""" from src.models import Buyer buyer = Buyer( name="Customer AG", street="Kundenweg 7", postal_code="54321", city="Kundenstadt", country="DE", vat_id="DE987654321", ) assert buyer.name == "Customer AG" assert buyer.street == "Kundenweg 7" assert buyer.vat_id == "DE987654321" class TestVatBreakdown: """Test VatBreakdown model.""" def test_vat_breakdown(self): """Test VatBreakdown model.""" from src.models import VatBreakdown vat = VatBreakdown(rate=19.0, base=99.90, amount=18.98) assert vat.rate == 19.0 assert vat.base == 99.90 assert vat.amount == 18.98 class TestPaymentTerms: """Test PaymentTerms model.""" def test_minimal_payment_terms(self): """Test PaymentTerms with minimal fields.""" from src.models import PaymentTerms terms = PaymentTerms() assert terms.iban is None assert terms.bic is None assert terms.account_holder is None def test_full_payment_terms(self): """Test PaymentTerms with all fields.""" from src.models import PaymentTerms terms = PaymentTerms( iban="DE89370400440532013000", bic="DEUTDEFF", account_holder="ACME GmbH" ) assert terms.iban == "DE89370400440532013000" assert terms.bic == "DEUTDEFF" assert terms.account_holder == "ACME GmbH" class TestTotals: """Test Totals model.""" def test_totals_minimal(self): """Test Totals with required fields only.""" from src.models import Totals totals = Totals(line_total_sum=99.90, net=99.90, vat_total=18.98, gross=118.88) assert totals.line_total_sum == 99.90 assert totals.net == 99.90 assert totals.vat_total == 18.98 assert totals.gross == 118.88 assert totals.vat_breakdown == [] def test_totals_with_vat_breakdown(self): """Test Totals with VAT breakdown.""" from src.models import Totals, VatBreakdown totals = Totals( line_total_sum=99.90, net=99.90, vat_total=18.98, gross=118.88, vat_breakdown=[VatBreakdown(rate=19.0, base=99.90, amount=18.98)], ) assert len(totals.vat_breakdown) == 1 assert totals.vat_breakdown[0].rate == 19.0 class TestLineItem: """Test LineItem model.""" def test_minimal_line_item(self): """Test LineItem with minimal required fields.""" from src.models import LineItem item = LineItem( position=1, description="Widget", quantity=10.0, unit="Stück", unit_price=9.99, line_total=99.90, ) assert item.position == 1 assert item.description == "Widget" assert item.quantity == 10.0 assert item.unit == "Stück" assert item.unit_price == 9.99 assert item.line_total == 99.90 assert item.article_number is None assert item.vat_rate is None def test_full_line_item(self): """Test LineItem with all fields.""" from src.models import LineItem item = LineItem( position=1, article_number="ART-001", article_number_buyer="KUN-001", description="Premium Widget", quantity=5.0, unit="Stück", unit_price=19.99, line_total=99.95, vat_rate=19.0, vat_amount=18.99, ) assert item.article_number == "ART-001" assert item.article_number_buyer == "KUN-001" assert item.vat_rate == 19.0 assert item.vat_amount == 18.99 class TestXmlData: """Test XmlData model.""" def test_minimal_xml_data(self): """Test XmlData with minimal required fields.""" from src.models import XmlData, Supplier, Buyer, Totals data = XmlData( invoice_number="RE-2025-001234", invoice_date="2025-02-04", supplier={"name": "ACME GmbH"}, buyer={"name": "Customer AG"}, line_items=[], totals={"line_total_sum": 0.0, "net": 0.0, "vat_total": 0.0, "gross": 0.0}, ) assert data.invoice_number == "RE-2025-001234" assert data.invoice_date == "2025-02-04" assert data.due_date is None assert data.notes is None def test_full_xml_data(self): """Test XmlData with all fields.""" from src.models import XmlData, Supplier, Buyer, LineItem, Totals, VatBreakdown data = XmlData( invoice_number="RE-2025-001234", invoice_date="2025-02-04", due_date="2025-03-04", supplier=Supplier(name="ACME GmbH", vat_id="DE123456789"), buyer=Buyer(name="Customer AG", vat_id="DE987654321"), line_items=[ LineItem( position=1, description="Widget", quantity=10.0, unit="Stück", unit_price=9.99, line_total=99.90, ) ], totals=Totals( line_total_sum=99.90, net=99.90, vat_breakdown=[VatBreakdown(rate=19.0, base=99.90, amount=18.98)], vat_total=18.98, gross=118.88, ), currency="EUR", notes="Payment due within 30 days", ) assert data.invoice_number == "RE-2025-001234" assert data.due_date == "2025-03-04" assert data.currency == "EUR" assert data.notes == "Payment due within 30 days" assert len(data.line_items) == 1 class TestExtractResponse: """Test ExtractResponse model.""" def test_extract_response_zugferd(self): """Test ExtractResponse with ZUGFeRD data.""" from src.models import ( ExtractResponse, XmlData, ExtractionMeta, Supplier, Buyer, LineItem, Totals, VatBreakdown, ) response = ExtractResponse( is_zugferd=True, zugferd_profil="EN16931", xml_raw="...", xml_data=XmlData( invoice_number="RE-2025-001234", invoice_date="2025-02-04", supplier=Supplier(name="ACME GmbH"), buyer=Buyer(name="Customer AG"), line_items=[ LineItem( position=1, description="Widget", quantity=10.0, unit="Stück", unit_price=9.99, line_total=99.90, ) ], totals=Totals( line_total_sum=99.90, net=99.90, vat_breakdown=[VatBreakdown(rate=19.0, base=99.90, amount=18.98)], vat_total=18.98, gross=118.88, ), ), pdf_text="Rechnung\n...", extraction_meta=ExtractionMeta( pages=1, xml_attachment_name="factur-x.xml", extraction_time_ms=234 ), ) assert response.is_zugferd is True assert response.zugferd_profil == "EN16931" assert response.xml_raw is not None assert response.xml_data is not None assert response.pdf_text is not None assert response.extraction_meta.pages == 1 def test_extract_response_non_zugferd(self): """Test ExtractResponse for non-ZUGFeRD PDF.""" from src.models import ExtractResponse, ExtractionMeta response = ExtractResponse( is_zugferd=False, pdf_text="Invoice text from PDF...", extraction_meta=ExtractionMeta(pages=1, extraction_time_ms=50), ) assert response.is_zugferd is False assert response.zugferd_profil is None assert response.xml_raw is None assert response.xml_data is None assert response.pdf_text is not None class TestErrorDetail: """Test ErrorDetail model.""" def test_error_detail_critical(self): """Test ErrorDetail with critical severity.""" from src.models import ErrorDetail error = ErrorDetail( check="pflichtfelder", field="invoice_number", error_code="missing_required_field", message="Invoice number is required", severity="critical", ) assert error.check == "pflichtfelder" assert error.field == "invoice_number" assert error.error_code == "missing_required_field" assert error.message == "Invoice number is required" assert error.severity == "critical" def test_error_detail_warning(self): """Test ErrorDetail with warning severity.""" from src.models import ErrorDetail error = ErrorDetail( check="pdf_abgleich", field=None, error_code="value_mismatch", message="Amounts differ slightly", severity="warning", ) assert error.check == "pdf_abgleich" assert error.field is None assert error.severity == "warning" class TestValidationResult: """Test ValidationResult model.""" def test_valid_result(self): """Test ValidationResult with no errors.""" from src.models import ValidationResult result = ValidationResult( is_valid=True, errors=[], warnings=[], summary=None, validation_time_ms=100 ) assert result.is_valid is True assert result.errors == [] assert result.warnings == [] assert result.summary is None assert result.validation_time_ms == 100 def test_invalid_result_with_errors(self): """Test ValidationResult with errors.""" from src.models import ValidationResult, ErrorDetail result = ValidationResult( is_valid=False, errors=[ ErrorDetail( check="pflichtfelder", field="invoice_number", error_code="missing_required_field", message="Invoice number is required", severity="critical", ), ErrorDetail( check="betraege", field="totals.gross", error_code="calculation_mismatch", message="Gross total mismatch: expected 118.88, got 118.90", severity="critical", ), ], warnings=[ ErrorDetail( check="pflichtfelder", field="due_date", error_code="missing_optional_field", message="Due date not provided", severity="warning", ) ], summary={"total_errors": 2, "total_warnings": 1, "critical_errors": 2}, validation_time_ms=150, ) assert result.is_valid is False assert len(result.errors) == 2 assert len(result.warnings) == 1 assert result.summary["total_errors"] == 2 assert result.validation_time_ms == 150 class TestExtractRequest: """Test ExtractRequest model.""" def test_extract_request(self): """Test ExtractRequest model.""" from src.models import ExtractRequest request = ExtractRequest(pdf_base64="JVBERi0xLjQK...") assert request.pdf_base64 == "JVBERi0xLjQK..." class TestValidateRequest: """Test ValidateRequest model.""" def test_validate_request_minimal(self): """Test ValidateRequest with minimal fields.""" from src.models import ValidateRequest request = ValidateRequest(xml_data={}, checks=["pflichtfelder"]) assert request.xml_data == {} assert request.checks == ["pflichtfelder"] assert request.pdf_text is None def test_validate_request_full(self): """Test ValidateRequest with all fields.""" from src.models import ValidateRequest request = ValidateRequest( xml_data={"invoice_number": "RE-001", "totals": {"gross": 118.88}}, pdf_text="Invoice text...", checks=["pflichtfelder", "betraege", "ustid", "pdf_abgleich"], ) assert request.xml_data["invoice_number"] == "RE-001" assert request.pdf_text is not None assert len(request.checks) == 4 class TestErrorResponse: """Test ErrorResponse model.""" def test_error_response(self): """Test ErrorResponse model.""" from src.models import ErrorResponse response = ErrorResponse( error="invalid_pdf", message="The provided file is not a valid PDF" ) assert response.error == "invalid_pdf" assert response.message == "The provided file is not a valid PDF" class TestModelsSerializeToJSON: """Test JSON serialization of all models.""" def test_extract_response_serializes(self): """Test ExtractResponse serializes to valid JSON.""" from src.models import ExtractResponse, ExtractionMeta response = ExtractResponse( is_zugferd=False, pdf_text="Invoice text...", extraction_meta=ExtractionMeta(pages=1, extraction_time_ms=50), ) json_str = response.model_dump_json() assert '"is_zugferd":false' in json_str assert '"pdf_text"' in json_str def test_validation_result_serializes(self): """Test ValidationResult serializes to valid JSON.""" from src.models import ValidationResult result = ValidationResult( is_valid=True, errors=[], warnings=[], summary=None, validation_time_ms=100 ) json_str = result.model_dump_json() assert '"is_valid":true' in json_str def test_models_roundtrip(self): """Test models survive JSON roundtrip.""" from src.models import Supplier supplier = Supplier(name="ACME GmbH", vat_id="DE123456789") json_str = supplier.model_dump_json() supplier2 = Supplier.model_validate_json(json_str) assert supplier2.name == supplier.name assert supplier2.vat_id == supplier.vat_id