class TestBaseValidator
Unit test class for testing the BaseValidator class functionality, including validation of extraction results, field types, date consistency, amount consistency, and entity-specific validation rules.
/tf/active/vicechatdev/invoice_extraction/tests/test_validators.py
75 - 204
moderate
Purpose
This test class provides comprehensive test coverage for the BaseValidator abstract class by creating a concrete implementation (ConcreteValidator) and testing all validation scenarios including required fields, field types, date consistency, amount calculations, and nested field access. It ensures the BaseValidator correctly identifies valid and invalid extraction results from document processing.
Source Code
class TestBaseValidator(unittest.TestCase):
"""Test cases for the BaseValidator class."""
class ConcreteValidator(BaseValidator):
"""Concrete implementation of BaseValidator for testing."""
def _entity_specific_validation(self, extraction_result, result):
# Simple implementation for testing
if 'test_field' in extraction_result:
if extraction_result['test_field'] == 'invalid':
result.add_issue('test_field', 'Test field is invalid', 'error')
def setUp(self):
"""Set up test environment before each test."""
self.config = {
'required_fields': {
'invoice.number': 'critical',
'vendor.name': 'critical',
'amounts.total': 'important'
}
}
self.validator = self.ConcreteValidator(self.config)
# Sample valid extraction result
self.valid_extraction = {
'invoice': {
'number': 'INV-12345',
'issue_date': '2023-01-15',
'due_date': '2023-02-15'
},
'vendor': {
'name': 'Test Vendor',
'address': '123 Test St',
'vat_number': '123456789'
},
'amounts': {
'subtotal': 500.00,
'tax': 100.00,
'total': 600.00,
'currency': 'USD'
}
}
def test_init(self):
"""Test initialization of BaseValidator."""
self.assertEqual(self.validator.required_fields['invoice.number'], 'critical')
self.assertIsInstance(self.validator.field_types, dict)
def test_validate_valid_extraction(self):
"""Test validation of a valid extraction result."""
result = self.validator.validate(self.valid_extraction)
self.assertTrue(result.is_valid)
self.assertEqual(len(result.issues), 0)
def test_validate_missing_required_field(self):
"""Test validation with a missing required field."""
# Remove a critical required field
invalid_extraction = self.valid_extraction.copy()
invalid_extraction['invoice'] = {} # No invoice number
result = self.validator.validate(invalid_extraction)
self.assertFalse(result.is_valid)
self.assertIn('invoice.number', result.field_issues)
def test_validate_field_types(self):
"""Test validation of field types."""
# Add invalid field type (string instead of number)
invalid_extraction = self.valid_extraction.copy()
invalid_extraction['amounts'] = {
'subtotal': 'five hundred', # Should be a number
'tax': 100.00,
'total': 600.00
}
result = self.validator.validate(invalid_extraction)
# Field type issues are warnings by default
self.assertTrue(result.is_valid) # Still valid despite warnings
self.assertEqual(len(result.warnings), 1)
def test_validate_date_consistency(self):
"""Test validation of date consistency."""
# Make due date before issue date
invalid_extraction = self.valid_extraction.copy()
invalid_extraction['invoice'] = {
'number': 'INV-12345',
'issue_date': '2023-02-15', # Later date
'due_date': '2023-01-15' # Earlier date
}
result = self.validator.validate(invalid_extraction)
self.assertFalse(result.is_valid)
self.assertIn('invoice.due_date', result.field_issues)
def test_validate_amount_consistency(self):
"""Test validation of amount consistency."""
# Make subtotal + tax != total
invalid_extraction = self.valid_extraction.copy()
invalid_extraction['amounts'] = {
'subtotal': 500.00,
'tax': 100.00,
'total': 700.00 # Incorrect total (should be 600)
}
result = self.validator.validate(invalid_extraction)
# Amount inconsistency issues are warnings by default
self.assertTrue(result.is_valid) # Still valid despite warnings
self.assertEqual(len(result.warnings), 1)
def test_entity_specific_validation(self):
"""Test entity-specific validation."""
# Add a test field that will trigger entity-specific validation
extraction = self.valid_extraction.copy()
extraction['test_field'] = 'invalid'
result = self.validator.validate(extraction)
self.assertFalse(result.is_valid)
self.assertIn('test_field', result.field_issues)
def test_get_nested_field(self):
"""Test getting nested fields by path."""
# Test existing field
value = self.validator._get_nested_field(self.valid_extraction, 'invoice.number')
self.assertEqual(value, 'INV-12345')
# Test non-existent field
value = self.validator._get_nested_field(self.valid_extraction, 'invoice.non_existent')
self.assertIsNone(value)
# Test non-existent parent
value = self.validator._get_nested_field(self.valid_extraction, 'non_existent.field')
self.assertIsNone(value)
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
unittest.TestCase | - |
Parameter Details
bases: Inherits from unittest.TestCase to provide testing framework functionality including assertions, test setup/teardown, and test discovery capabilities
Return Value
As a test class, it doesn't return values directly. Each test method returns None but uses assertions to verify expected behavior. Test methods validate that the BaseValidator produces correct ValidationResult objects with appropriate is_valid flags, issues, warnings, and field_issues dictionaries.
Class Interface
Methods
setUp(self) -> None
Purpose: Initialize test environment before each test method runs, creating validator instance and sample data
Returns: None - sets up instance attributes self.config, self.validator, and self.valid_extraction
test_init(self) -> None
Purpose: Test that BaseValidator initializes correctly with required_fields and field_types
Returns: None - uses assertions to verify initialization
test_validate_valid_extraction(self) -> None
Purpose: Test that a valid extraction result passes validation without issues
Returns: None - asserts result.is_valid is True and no issues exist
test_validate_missing_required_field(self) -> None
Purpose: Test that validation fails when a critical required field is missing
Returns: None - asserts result.is_valid is False and field_issues contains the missing field
test_validate_field_types(self) -> None
Purpose: Test that field type validation detects incorrect data types (e.g., string instead of number)
Returns: None - asserts warnings are generated for type mismatches
test_validate_date_consistency(self) -> None
Purpose: Test that date logic validation detects inconsistencies like due_date before issue_date
Returns: None - asserts result.is_valid is False when dates are inconsistent
test_validate_amount_consistency(self) -> None
Purpose: Test that amount calculations are validated (subtotal + tax = total)
Returns: None - asserts warnings are generated for amount inconsistencies
test_entity_specific_validation(self) -> None
Purpose: Test that entity-specific validation rules defined in ConcreteValidator are executed
Returns: None - asserts custom validation logic is triggered and issues are recorded
test_get_nested_field(self) -> None
Purpose: Test the _get_nested_field method for accessing nested dictionary values using dot notation
Returns: None - asserts correct values are returned for existing fields and None for missing fields
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
config |
dict | Configuration dictionary containing required_fields mapping with field paths and criticality levels (critical, important) | instance |
validator |
ConcreteValidator | Instance of ConcreteValidator (BaseValidator subclass) used for testing validation functionality | instance |
valid_extraction |
dict | Sample valid extraction result containing nested dictionaries for invoice, vendor, and amounts data used as baseline for tests | instance |
ConcreteValidator |
class | Inner class that extends BaseValidator to provide concrete implementation of _entity_specific_validation for testing purposes | class |
Dependencies
unittestdatetimeloggingvalidators.base_validatorvalidators.uk_validatorvalidators.be_validatorvalidators.au_validator
Required Imports
import unittest
from datetime import datetime
import logging
from validators.base_validator import BaseValidator
from validators.base_validator import ValidationResult
from validators.uk_validator import UKValidator
from validators.be_validator import BEValidator
from validators.au_validator import AUValidator
Usage Example
import unittest
from validators.base_validator import BaseValidator, ValidationResult
class TestBaseValidator(unittest.TestCase):
class ConcreteValidator(BaseValidator):
def _entity_specific_validation(self, extraction_result, result):
if 'test_field' in extraction_result:
if extraction_result['test_field'] == 'invalid':
result.add_issue('test_field', 'Test field is invalid', 'error')
def setUp(self):
self.config = {
'required_fields': {
'invoice.number': 'critical',
'vendor.name': 'critical',
'amounts.total': 'important'
}
}
self.validator = self.ConcreteValidator(self.config)
self.valid_extraction = {
'invoice': {'number': 'INV-12345', 'issue_date': '2023-01-15'},
'vendor': {'name': 'Test Vendor'},
'amounts': {'subtotal': 500.00, 'tax': 100.00, 'total': 600.00}
}
def test_validate_valid_extraction(self):
result = self.validator.validate(self.valid_extraction)
self.assertTrue(result.is_valid)
self.assertEqual(len(result.issues), 0)
if __name__ == '__main__':
unittest.main()
Best Practices
- Always call setUp() before each test to ensure clean test state with fresh validator and extraction data
- Use copy() when modifying valid_extraction to avoid test interdependencies
- Test both valid and invalid scenarios to ensure comprehensive coverage
- Verify both is_valid flag and specific issue collections (issues, warnings, field_issues)
- Create a ConcreteValidator subclass to test abstract BaseValidator since it cannot be instantiated directly
- Test nested field access patterns using dot notation (e.g., 'invoice.number')
- Distinguish between critical errors (is_valid=False) and warnings (is_valid=True with warnings)
- Test edge cases like missing fields, incorrect types, and logical inconsistencies
- Use descriptive test method names that clearly indicate what is being tested
- Group related assertions together to make test failures easier to diagnose
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class TestBaseExtractor 77.7% similar
-
class BaseValidator 77.3% similar
-
class TestBEValidator 74.7% similar
-
class TestUKValidator 70.1% similar
-
class TestAUValidator 67.5% similar