Files
motovaultpro/mvp-platform-services/vehicles/etl/tests/test_make_name_mapper.py
Eric Gullickson a052040e3a Initial Commit
2025-09-17 16:09:15 -05:00

285 lines
11 KiB
Python

"""
Unit Tests for MakeNameMapper
Tests the make name normalization functionality including:
- Standard filename to display name conversion
- Special capitalization cases (BMW, GMC, etc.)
- Multi-word names (underscore to space conversion)
- Validation against authoritative makes list
- Error handling for edge cases
"""
import unittest
import tempfile
import json
import os
from unittest.mock import patch, mock_open
# Import the class we're testing
from ..utils.make_name_mapper import MakeNameMapper, ValidationReport
class TestMakeNameMapper(unittest.TestCase):
"""Test cases for MakeNameMapper utility"""
def setUp(self):
"""Set up test environment before each test"""
self.mapper = MakeNameMapper()
def test_normalize_single_word_makes(self):
"""Test normalization of single-word make names"""
test_cases = [
('toyota.json', 'Toyota'),
('honda.json', 'Honda'),
('ford.json', 'Ford'),
('audi.json', 'Audi'),
('tesla.json', 'Tesla'),
]
for filename, expected in test_cases:
with self.subTest(filename=filename):
result = self.mapper.normalize_make_name(filename)
self.assertEqual(result, expected,
f"Expected '{expected}' for '{filename}', got '{result}'")
def test_normalize_multi_word_makes(self):
"""Test normalization of multi-word make names (underscore to space)"""
test_cases = [
('alfa_romeo.json', 'Alfa Romeo'),
('land_rover.json', 'Land Rover'),
('rolls_royce.json', 'Rolls Royce'),
('aston_martin.json', 'Aston Martin'),
]
for filename, expected in test_cases:
with self.subTest(filename=filename):
result = self.mapper.normalize_make_name(filename)
self.assertEqual(result, expected,
f"Expected '{expected}' for '{filename}', got '{result}'")
def test_normalize_special_cases(self):
"""Test special capitalization cases"""
test_cases = [
('bmw.json', 'BMW'),
('gmc.json', 'GMC'),
('mini.json', 'MINI'),
('mclaren.json', 'McLaren'),
]
for filename, expected in test_cases:
with self.subTest(filename=filename):
result = self.mapper.normalize_make_name(filename)
self.assertEqual(result, expected,
f"Expected '{expected}' for '{filename}', got '{result}'")
def test_normalize_edge_cases(self):
"""Test edge cases and error handling"""
test_cases = [
# Edge cases that should still work
('test.json', 'Test'),
('test_brand.json', 'Test Brand'),
# Error cases that should return "Unknown"
('.json', 'Unknown'),
('', 'Unknown'),
]
for filename, expected in test_cases:
with self.subTest(filename=filename):
result = self.mapper.normalize_make_name(filename)
self.assertEqual(result, expected,
f"Expected '{expected}' for '{filename}', got '{result}'")
def test_validate_mapping_valid_makes(self):
"""Test validation of valid make names"""
# These should be in the authoritative list
valid_cases = [
('toyota.json', 'Toyota'),
('alfa_romeo.json', 'Alfa Romeo'),
('bmw.json', 'BMW'),
('land_rover.json', 'Land Rover'),
]
for filename, display_name in valid_cases:
with self.subTest(filename=filename):
is_valid = self.mapper.validate_mapping(filename, display_name)
self.assertTrue(is_valid, f"'{display_name}' should be valid")
def test_validate_mapping_invalid_makes(self):
"""Test validation of invalid make names"""
invalid_cases = [
('unknown.json', 'Unknown Brand'),
('fake.json', 'Fake'),
('test.json', 'Test Make'),
]
for filename, display_name in invalid_cases:
with self.subTest(filename=filename):
is_valid = self.mapper.validate_mapping(filename, display_name)
self.assertFalse(is_valid, f"'{display_name}' should be invalid")
def test_reverse_lookup(self):
"""Test reverse lookup functionality"""
test_cases = [
('Toyota', 'toyota.json'),
('Alfa Romeo', 'alfa_romeo.json'),
('BMW', 'bmw.json'),
('Land Rover', 'land_rover.json'),
('McLaren', 'mclaren.json'),
]
for display_name, expected_filename in test_cases:
with self.subTest(display_name=display_name):
result = self.mapper.get_filename_for_display_name(display_name)
self.assertEqual(result, expected_filename,
f"Expected '{expected_filename}' for '{display_name}', got '{result}'")
def test_get_all_mappings(self):
"""Test getting all mappings from a directory"""
# Create temporary directory with test files
with tempfile.TemporaryDirectory() as temp_dir:
# Create test JSON files
test_files = ['toyota.json', 'alfa_romeo.json', 'bmw.json']
for filename in test_files:
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'w') as f:
json.dump({"test": "data"}, f)
mappings = self.mapper.get_all_mappings(temp_dir)
# Check that all files were processed
self.assertEqual(len(mappings), len(test_files))
# Check specific mappings
expected_mappings = {
'toyota.json': 'Toyota',
'alfa_romeo.json': 'Alfa Romeo',
'bmw.json': 'BMW'
}
for filename, expected_display in expected_mappings.items():
self.assertIn(filename, mappings)
self.assertEqual(mappings[filename], expected_display)
def test_validation_report(self):
"""Test validation report generation"""
# Create temporary directory with test files
with tempfile.TemporaryDirectory() as temp_dir:
# Mix of valid and invalid files
test_files = {
'toyota.json': {"test": "data"}, # Valid
'alfa_romeo.json': {"test": "data"}, # Valid
'unknown_brand.json': {"test": "data"} # Invalid
}
for filename, content in test_files.items():
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'w') as f:
json.dump(content, f)
report = self.mapper.validate_all_mappings(temp_dir)
# Check report structure
self.assertIsInstance(report, ValidationReport)
self.assertEqual(report.total_files, 3)
self.assertEqual(report.valid_mappings, 2) # toyota and alfa_romeo should be valid
self.assertEqual(len(report.mismatches), 1) # unknown_brand should be invalid
# Check success rate
expected_rate = 2/3 # 2 valid out of 3 total
self.assertAlmostEqual(report.success_rate, expected_rate, places=2)
# Check mismatch details
mismatch = report.mismatches[0]
self.assertEqual(mismatch['filename'], 'unknown_brand.json')
self.assertEqual(mismatch['mapped_name'], 'Unknown Brand')
self.assertEqual(mismatch['status'], 'NOT_FOUND_IN_AUTHORITATIVE')
@patch('builtins.open', mock_open(read_data='{"manufacturers": ["Toyota", "BMW", "Custom Brand"]}'))
def test_load_custom_authoritative_makes(self):
"""Test loading custom authoritative makes from file"""
with patch('os.path.exists', return_value=True):
mapper = MakeNameMapper(sources_dir='test_sources')
# Should have loaded the custom list
self.assertIn('Toyota', mapper.authoritative_makes)
self.assertIn('BMW', mapper.authoritative_makes)
self.assertIn('Custom Brand', mapper.authoritative_makes)
# Test validation with custom list
self.assertTrue(mapper.validate_mapping('custom.json', 'Custom Brand'))
def test_make_statistics(self):
"""Test make statistics calculation"""
with tempfile.TemporaryDirectory() as temp_dir:
# Create test files representing different categories
test_files = [
'toyota.json', # Single word
'honda.json', # Single word
'alfa_romeo.json', # Multi word
'land_rover.json', # Multi word
'bmw.json', # Special case
'gmc.json', # Special case
]
for filename in test_files:
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'w') as f:
json.dump({"test": "data"}, f)
stats = self.mapper.get_make_statistics(temp_dir)
expected_stats = {
'total': 6,
'single_words': 2, # toyota, honda
'multi_words': 2, # alfa_romeo, land_rover
'special_cases': 2 # bmw, gmc
}
self.assertEqual(stats, expected_stats)
def test_error_handling(self):
"""Test error handling for various failure scenarios"""
# Test with non-existent directory
mappings = self.mapper.get_all_mappings('/non/existent/directory')
self.assertEqual(mappings, {})
# Test validation report with non-existent directory
report = self.mapper.validate_all_mappings('/non/existent/directory')
self.assertEqual(report.total_files, 0)
self.assertEqual(report.valid_mappings, 0)
self.assertEqual(len(report.mismatches), 0)
class TestValidationReport(unittest.TestCase):
"""Test cases for ValidationReport dataclass"""
def test_success_rate_calculation(self):
"""Test success rate calculation"""
# Test normal case
report = ValidationReport(
total_files=10,
valid_mappings=8,
mismatches=[]
)
self.assertEqual(report.success_rate, 0.8)
# Test zero division case
report_empty = ValidationReport(
total_files=0,
valid_mappings=0,
mismatches=[]
)
self.assertEqual(report_empty.success_rate, 0.0)
# Test perfect score
report_perfect = ValidationReport(
total_files=5,
valid_mappings=5,
mismatches=[]
)
self.assertEqual(report_perfect.success_rate, 1.0)
if __name__ == '__main__':
unittest.main()