""" Unit tests for the Audi Stock Car Service parser and normalization logic. """ from datetime import date from decimal import Decimal import pytest from oem_crawler.crawlers.base import CrawlerRegistry from oem_crawler.crawlers.audi.parser import ( map_audi_drivetrain, map_audi_fuel_type, map_audi_series, map_audi_transmission, parse_audi_vehicle, parse_german_date, parse_power, ) from oem_crawler.db.schemas import ( BatteryCapacityUnit, DrivetrainType, FuelType, TransmissionType, VehicleCondition, VehicleStatus, ) class TestAudiParser: def test_registry_contains_audi(self): assert "audi" in CrawlerRegistry.supported_brands() crawler_cls = CrawlerRegistry.get("audi") crawler = crawler_cls(market_country="DE") assert crawler.brand_name == "AUDI" assert crawler.get_market_code("new") == "de" assert crawler.get_market_code("used") == "deuc" def test_map_fuel_type(self): assert map_audi_fuel_type({"code": "E", "description": "Elektro"}) == FuelType.BEV assert map_audi_fuel_type("Elektro") == FuelType.BEV assert map_audi_fuel_type({"code": "H", "description": "Hybrid (Benzin/Elektro)"}) == FuelType.PHEV assert map_audi_fuel_type({"code": "B", "description": "Benzin"}) == FuelType.PETROL assert map_audi_fuel_type({"code": "D", "description": "Diesel"}) == FuelType.DIESEL assert map_audi_fuel_type("Mild-Hybrid") == FuelType.MHEV assert map_audi_fuel_type("g-tron Erdgas") == FuelType.CNG def test_map_transmission(self): assert map_audi_transmission({"code": "s-tronic", "description": "S tronic"}) == TransmissionType.AUTOMATIC assert map_audi_transmission("tiptronic") == TransmissionType.AUTOMATIC assert map_audi_transmission("Automatik") == TransmissionType.AUTOMATIC assert map_audi_transmission({"code": "manual", "description": "Handschaltung"}) == TransmissionType.MANUAL assert map_audi_transmission("Schaltgetriebe") == TransmissionType.MANUAL assert map_audi_transmission(None) is None def test_map_drivetrain(self): assert map_audi_drivetrain({"code": "quattro", "description": "quattro"}) == DrivetrainType.AWD assert map_audi_drivetrain("Allradantrieb") == DrivetrainType.AWD assert map_audi_drivetrain({"code": "fwd", "description": "Frontantrieb"}) == DrivetrainType.FWD assert map_audi_drivetrain("Heckantrieb") == DrivetrainType.RWD assert map_audi_drivetrain(None) is None def test_map_series(self): assert map_audi_series({"description": "A3"}) == "A3" assert map_audi_series({"description": "Q4 e-tron"}) == "Q4 e-tron" assert map_audi_series(None, "Audi A1 Sportback 25 TFSI") == "A1" assert map_audi_series(None, "Audi e-tron GT quattro 350 kW") == "e-tron GT" assert map_audi_series(None, "Audi RS6 Avant performance TFSI") == "RS6" assert map_audi_series(None, "Audi Q8 50 TDI quattro") == "Q8" def test_parse_power(self): kw, hp = parse_power("110 kW (150 PS)") assert kw == 110 assert hp == 150 kw2, hp2 = parse_power("70 kW") assert kw2 == 70 assert hp2 == 95 kw3, hp3 = parse_power(None) assert kw3 is None assert hp3 is None def test_parse_german_date(self): assert parse_german_date("21.07.2015") == date(2015, 7, 21) assert parse_german_date("2021-10-20") == date(2021, 10, 20) assert parse_german_date(1319068800000) == date(2011, 10, 20) assert parse_german_date(None) is None def test_parse_audi_new_ice_vehicle(self): basic_item = { "id": "REVVMjA2NjUxNzdBQlNXMzQyMDI1=", "carId": "DEU20665177ABSW342025", "decodedCarId": "DEU20665177ABSW342025", "commissionNumber": "177ABSW342025", "model": { "code": "aacv", "description": "Audi A1 Sportback 25 TFSI 70 kW (95 PS) Schaltgetriebe", }, "symbolicCarlineGroup": { "description": "A1", }, "bodyType": { "description": "Sportback", }, "driveType": { "code": "drive.fwd", "description": "Frontantrieb", }, "gearType": { "code": "gear-type.manual", "description": "Handschaltung", }, "fuel": { "code": "B", "description": "Benzin", }, "powerDisplay": "70 kW (95 PS)", "modelYear": 2025, "typedPrices": [ {"amount": 356.0, "currencyCode": "EUR", "type": "financeRate"}, {"amount": 21480.0, "currencyCode": "EUR", "type": "retail"}, ], "dealer": { "id": "20665", "name": "Autohaus Schnitzler GmbH & Co. KG", "street": "Westring 20-22", "zipCode": "40721", "city": "Hilden", "geoLocation": { "lat": 51.17704, "lon": 6.91641, }, }, "used": {}, "extColor": {"description": "Gletscherweiß Metallic", "code": "2Y2Y"}, "intColor": {"description": "Schwarz"}, "tilesPictures": ["https://mediaservice.audi.com/media/tile.jpg"], "pictures": ["https://mediaservice.audi.com/media/full.jpg"], } v = parse_audi_vehicle(basic_item=basic_item, market_country="DE", requested_stock_type="new") assert v.brand == "AUDI" assert v.oem_vehicle_id == "DEU20665177ABSW342025" assert v.series == "A1" assert v.model_name == "Audi A1 Sportback 25 TFSI 70 kW (95 PS) Schaltgetriebe" assert v.body_type == "Sportback" assert v.condition == VehicleCondition.NEW assert v.fuel_type == FuelType.PETROL assert v.transmission == TransmissionType.MANUAL assert v.drivetrain == DrivetrainType.FWD assert v.power_kw == 70 assert v.power_hp == 95 assert v.model_year == 2025 assert v.mileage_km == 0 assert v.price_gross == Decimal("21480.0") assert v.monthly_rate == Decimal("356.0") assert v.dealer_name == "Autohaus Schnitzler GmbH & Co. KG" assert v.dealer_id == "20665" assert v.location_city == "Hilden" assert v.location_postal_code == "40721" assert v.latitude == Decimal("51.17704") assert v.longitude == Decimal("6.91641") assert v.exterior_color == "Gletscherweiß Metallic" assert v.status == VehicleStatus.IN_STOCK assert "REVVMjA2NjUxNzdBQlNXMzQyMDI1=" in v.vehicle_url assert len(v.image_urls) == 2 def test_parse_audi_used_vehicle(self): basic_item = { "id": "REVVMjIyNjNHVzE4ODMw=", "carId": "DEU22263GW18830", "model": { "description": "Audi A3 Sportback 1.4 TFSI attraction", }, "symbolicCarlineGroup": { "description": "A3", }, "fuel": { "code": "B", "description": "Benzin", }, "gearType": { "description": "Handschaltung", }, "driveType": { "description": "Frontantrieb", }, "powerDisplay": "92 kW (125 PS)", "typedPrices": [ {"amount": 10890.0, "currencyCode": "EUR", "type": "retail"} ], "dealer": { "name": "Autohaus Pietsch.Bünde GmbH", "city": "Bünde", "zipCode": "32257", "geoLocation": {"lat": 52.2025, "lon": 8.57877}, }, "warrantyPlus": True, "used": { "initialRegistration": "21.07.2015", "mileage": 108859.0, "numPreviousOwners": 2, "pictureUrls": ["https://vtpimages.audi.com/carimg2/used.jpg"], }, } v = parse_audi_vehicle(basic_item=basic_item, market_country="DE", requested_stock_type="used") assert v.brand == "AUDI" assert v.series == "A3" assert v.condition == VehicleCondition.CERTIFIED_PRE_OWNED # warrantyPlus is True assert v.mileage_km == 108859 assert v.first_registration_date == date(2015, 7, 21) assert v.previous_owners_count == 2 assert v.price_gross == Decimal("10890.0") assert v.latitude == Decimal("52.2025") assert v.longitude == Decimal("8.57877") assert "gebrauchtwagenboerse" in v.vehicle_url def test_parse_audi_ev_with_detail(self): basic_item = { "id": "REVVNDcyNTIxNzdaWlpZNDMyMDI2=", "carId": "DEU47252177ZZZY432026", "model": { "description": "Audi Q4 Sportback e-tron 150 kW", }, "symbolicCarlineGroup": { "description": "Q4 e-tron", }, "fuel": { "code": "E", "description": "Elektro", }, "gearType": { "description": "Automatik", }, "powerDisplay": "150 kW (204 PS)", "typedPrices": [ {"amount": 48900.0, "currencyCode": "EUR", "type": "retail"} ], "dealer": { "name": "Audi Zentrum Hamburg", "city": "Hamburg", "zipCode": "22525", "geoLocation": {"lat": 53.58, "lon": 9.92}, }, } detail_data = { "vehicle": { "detail": { "techData": { "fields": [ {"name": "Netto-Batterie-Kapazität", "value": "59 kWh"}, {"name": "Brutto-Batterie-Kapazität", "value": "63 kWh"}, {"name": "Max. Ladeleistung AC / HPC", "value": "11 kW / 160 kW"}, {"name": "Ladedauer DC 10-80% bei max. Ladeleistung", "value": "27 min"}, {"name": "Elektrische Reichweite kombiniert (WLTP)", "value": "415 km"}, ] }, "features": [ { "key": "5SL", "prno": "MASL5SL", "name": "Außenspiegel links, asphärisch", "groupName": "Außenausstattung", "kind": "serie", }, { "key": "KA2", "prno": "MKA2KA2", "name": "Rückfahrkamera", "groupName": "Assistenzsysteme", "kind": "sonder", }, ], } } } v = parse_audi_vehicle( basic_item=basic_item, detail_data=detail_data, market_country="DE", requested_stock_type="new", ) assert v.brand == "AUDI" assert v.series == "Q4 e-tron" assert v.fuel_type == FuelType.BEV assert v.battery_capacity == Decimal("59") assert v.battery_capacity_net_kwh == Decimal("59") assert v.battery_capacity_gross_kwh == Decimal("63") assert v.battery_capacity_unit == BatteryCapacityUnit.KWH assert v.charging_power_ac_kw == Decimal("11") assert v.charging_power_dc_kw == Decimal("160") assert v.charging_time_dc_minutes == 27 assert v.electric_range_wltp_km == 415 # Check options assert len(v.options) == 2 assert v.options[0].option_code == "5SL" assert v.options[0].is_standard is True assert v.options[1].option_code == "KA2" assert v.options[1].is_standard is False assert v.options[1].category == "Assistenzsysteme" def test_fetch_search_page_pagination_params(self): from unittest.mock import MagicMock from oem_crawler.crawlers.audi.crawler import AudiStockCrawler crawler = AudiStockCrawler(market_country="DE") mock_client = MagicMock() mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.json.return_value = {"totalCount": 100, "vehicleBasic": []} mock_client.get.return_value = mock_resp crawler.fetch_search_page(mock_client, market_code="de", page=2, size=50) mock_client.get.assert_called_once() call_kwargs = mock_client.get.call_args.kwargs assert call_kwargs["params"]["from"] == 100 assert call_kwargs["params"]["size"] == 50