import pandas as pd import pytest from unittest.mock import Mock from epc_api.client import EpcClient from backend.Property import Property from etl.epc_clean.EpcClean import EpcClean # Define some test data mock_epc_response = { "rows": [ { "lmk-key": 1, "uprn": 1, "number-habitable-rooms": 5, "property-type": "House", "built-form": "Detached", "inspection-date": "2023-06-01", 'lodgement-datetime': '2023-06-01 20:29:01', "some-other-key": "some-value", "roof-description": "Roof Description", "walls-description": "Walls Description", "windows-description": "Windows Description", "mainheat-description": "Main Heating Description", "hotwater-description": "Hot Water Description", "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", "energy-consumption-current": "50", "co2-emissions-current": "123", "mechanical-ventilation": "natural", 'photo-supply': 0, "solar-water-heating-flag": "N", "wind-turbine-count": 0, "extension-count": 0, "heat-loss-corridor": "no corridor", "unheated-corridor-length": 0, "mains-gas-flag": "Y", "floor-height": 2.5, "total-floor-area": 100, "construction-age-band": "England and Wales: 1967-1975", "floor-description": "Floor Description", "floor-level": "Ground" }, { "lmk-key": 2, "uprn": 2, "number-habitable-rooms": 5, "property-type": "House", "built-form": "Detached", "inspection-date": "2023-05-01", 'lodgement-datetime': '2023-05-01 20:29:01', "some-other-key": "some-other-value", "roof-description": "Roof Description", "walls-description": "Walls Description", "windows-description": "Windows Description", "mainheat-description": "Main Heating Description", "hotwater-description": "Hot Water Description", "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", "energy-consumption-current": "50", "co2-emissions-current": "123", "mechanical-ventilation": "natural", 'photo-supply': 0, "solar-water-heating-flag": "N", "wind-turbine-count": 0, "extension-count": 0, "heat-loss-corridor": "no corridor", "unheated-corridor-length": 0, "mains-gas-flag": "Y", "floor-height": 2.5, "total-floor-area": 100, "construction-age-band": "England and Wales: 1967-1975", "floor-description": "Floor Description", "floor-level": "Ground" } ] } mock_epc_response_dupe = { 'rows': [ { "lmk-key": 1, "uprn": 1, "number-habitable-rooms": 5, "property-type": "House", 'inspection-date': '2023-06-01', 'lodgement-datetime': '2023-06-01 20:29:01', 'some-other-key': 'some-value', 'roof-description': 'Roof Description', 'walls-description': 'Walls Description', 'windows-description': 'Windows Description', 'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", "energy-consumption-current": "50", "co2-emissions-current": "123", "mechanical-ventilation": "natural", 'photo-supply': 0, "solar-water-heating-flag": "N", "wind-turbine-count": 0, "extension-count": 0, "heat-loss-corridor": "no corridor", "unheated-corridor-length": 0, "mains-gas-flag": "Y", "floor-height": 2.5, "total-floor-area": 100, "construction-age-band": "England and Wales: 1967-1975", "floor-description": "Floor Description", "floor-level": "Ground" }, { "lmk-key": 2, "uprn": 2, "number-habitable-rooms": 5, "property-type": "House", 'inspection-date': '2023-05-01', 'lodgement-datetime': '2023-05-01 20:29:01', 'some-other-key': 'some-other-value', 'roof-description': 'Roof Description', 'walls-description': 'Walls Description', 'windows-description': 'Windows Description', 'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", "energy-consumption-current": "50", "co2-emissions-current": "123", "mechanical-ventilation": "natural", 'photo-supply': 0, "solar-water-heating-flag": "N", "wind-turbine-count": 0, "extension-count": 0, "heat-loss-corridor": "no corridor", "unheated-corridor-length": 0, "mains-gas-flag": "Y", "floor-height": 2.5, "total-floor-area": 100, "construction-age-band": "England and Wales: 1967-1975", "floor-description": "Floor Description", "floor-level": "Ground" }, { "lmk-key": 3, "uprn": 3, "number-habitable-rooms": 5, "property-type": "House", 'inspection-date': '2023-06-01', 'lodgement-datetime': '2023-06-01 20:29:01', 'some-other-key': 'duplicate-date', 'roof-description': 'Roof Description', 'walls-description': 'Walls Description', 'windows-description': 'Windows Description', 'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", "energy-consumption-current": "50", "co2-emissions-current": "123", "mechanical-ventilation": "natural", 'photo-supply': 0, "solar-water-heating-flag": "N", "wind-turbine-count": 0, "extension-count": 0, "heat-loss-corridor": "no corridor", "unheated-corridor-length": 0, "mains-gas-flag": "Y", "floor-height": 2.5, "total-floor-area": 100, "construction-age-band": "England and Wales: 1967-1975", "floor-description": "Floor Description", "floor-level": "Ground" } ] } class TestProperty: @pytest.fixture(autouse=True) def property_instance(self, mock_epc_client, mock_cleaner): property_instance = Property(1, "AB12CD", "Test Address", epc_client=mock_epc_client) return property_instance @pytest.fixture(autouse=True) def property_instance_dupe_data(self, mock_epc_client_dupe_data): property_instance_dupe_data = Property(2, "AB12CD", "Test Address", epc_client=mock_epc_client_dupe_data) return property_instance_dupe_data @pytest.fixture def mock_epc_client(self): mock_epc_client = Mock(spec=EpcClient(auth_token="mocked_auth_token")) mock_epc_client.domestic.search.return_value = mock_epc_response.copy() mock_epc_client.auth_token = "mocked_auth_token" return mock_epc_client @pytest.fixture def mock_epc_client_dupe_data(self): mock_epc_client_dupe_data = Mock(spec=EpcClient(auth_token="mocked_auth_token")) mock_epc_client_dupe_data.domestic.search.return_value = mock_epc_response_dupe.copy() mock_epc_client_dupe_data.auth_token = "mocked_auth_token" return mock_epc_client_dupe_data @pytest.fixture def mock_cleaner(self): lighting_averages = [ {'lighting-description': 'good lighting efficiency', 'low-energy-lighting': 99.26666666666667}, {'lighting-description': 'excellent lighting efficiency', 'low-energy-lighting': 100.0}, {'lighting-description': 'below average lighting efficiency', 'low-energy-lighting': 0.0} ] cleaner_spec = EpcClean( data=[ {"roof-description": "Roof Description"}, {"walls-description": "Walls Description"}, {"windows-description": "Windows Description"}, {"mainheat-description": "Main Heating Description"}, {"hotwater-description": "Hot Water Description"}, {"lighting-description": "Good Lighting Efficiency"}, {"low-energy-lighting": 0}, {"floor-description": "Floor Description"} ], lighting_averages=lighting_averages ) mock_cleaner = Mock(spec=cleaner_spec) walls_data = { "original_description": "Walls Description", "is_cavity_wall": True, "is_solid_brick": False, "is_timber_frame": False, "is_system_built": False, "is_park_home": False, "is_cob": False, "is_sandstone_or_limestone": False, "is_granite_or_whinstone": False, } mock_cleaner.cleaned = { "roof-description": [{"original_description": "Roof Description"}], "walls-description": [walls_data], "windows-description": [{"original_description": "Windows Description"}], "mainheat-description": [{"original_description": "Main Heating Description"}], "hotwater-description": [{"original_description": "Hot Water Description"}], "lighting-description": [{"original_description": "Good Lighting Efficiency"}], "floor-description": [ {"original_description": "Floor Description", "is_suspended": True, "another_property_below": False}] } return mock_cleaner def test_init(self, mock_epc_client): inst1 = Property(0, "AB12CD", "Test Address", epc_client=mock_epc_client) # Should be mocked auth token assert inst1.epc_client.auth_token == "mocked_auth_token" inst2 = Property(3, "AB12CD", "Test Address", epc_client=mock_epc_client) assert inst2.epc_client.auth_token inst3 = Property(4, "AB12CD", "Test Address", data={"some": "data"}, epc_client=mock_epc_client) assert inst3.data == {"some": "data"} data = inst3.search_address_epc() assert data is None def test_search_address_epc(self, property_instance): # Call the method to test property_instance.search_address_epc() # Verify that the correct data is being returned assert property_instance.data == mock_epc_response["rows"][0] def test_search_address_epc_multiple_results(self, property_instance_dupe_data, mock_epc_client_dupe_data): with pytest.raises(Exception, match="More than one result found for this address - investigate me"): property_instance_dupe_data.search_address_epc() def test_get_components(self, property_instance, mock_cleaner, mock_epc_client): property_instance.search_address_epc() property_instance.get_components(mock_cleaner.cleaned) # Verify that the components are set correctly assert property_instance.roof == {"original_description": "Roof Description"} assert property_instance.walls == { "original_description": "Walls Description", "is_cavity_wall": True, "is_solid_brick": False, "is_timber_frame": False, "is_system_built": False, "is_park_home": False, "is_cob": False, "is_sandstone_or_limestone": False, "is_granite_or_whinstone": False, } assert property_instance.windows == {"original_description": "Windows Description"} assert property_instance.main_heating == {"original_description": "Main Heating Description"} assert property_instance.hotwater == {"original_description": "Hot Water Description"} assert property_instance.wall_type == "cavity" def test_get_components_without_cleaned_data(self, property_instance, mock_cleaner): # Modify the mock EpcClean to not have cleaned data mock_cleaner.cleaned = {} # Verify that ValueError is raised when EpcClean doesn't contain cleaned data with pytest.raises(ValueError, match="Cleaner does not contain cleaned data"): property_instance.get_components(mock_cleaner.cleaned) def test_get_components_no_data(self, property_instance, mock_cleaner): # Modify the mock cleaner to have no attributes for a specific description mock_cleaner.cleaned = { "roof-description": [] } # Verify that ValueError is raised when no attributes are found with pytest.raises(ValueError, match="Property does not contain data"): property_instance.get_components(mock_cleaner.cleaned) def test_get_components_no_attributes(self, property_instance, mock_cleaner): # Modify the mock cleaner to have no attributes for a specific description mock_cleaner.cleaned = { "roof-description": [] } property_instance.search_address_epc() property_instance.data["roof-description"] = "Pitched, no insulation" property_instance.walls = { "original_description": "Walls Description", "is_cavity_wall": True, "is_solid_brick": False, "is_timber_frame": False, "is_system_built": False, "is_park_home": False, "is_cob": False, "is_sandstone_or_limestone": False, "is_granite_or_whinstone": False, } property_instance.floor = { "is_suspended": False, "another_property_below": False, "is_solid": True } # Assert backup cleaning has been applied property_instance.get_components(mock_cleaner.cleaned) assert property_instance.roof["clean_description"] == "Pitched, no insulation" assert property_instance.roof["is_pitched"] def test_get_components_multiple_attributes(self, property_instance, mock_cleaner): # This shouldn't happen - it would mean a cleaning error property_instance.search_address_epc() property_instance.data["roof-description"] = "Roof Description" cleaned = { "roof-description": [ {"original_description": "Roof Description"}, {"original_description": "Roof Description"} ] } # Verify that ValueError is raised when multiple attributes are found with pytest.raises(ValueError, match="Either No attributes or multiple found for roof-description"): property_instance.get_components(cleaned) def test_set_spatial(self, mock_epc_client): prop = Property(1, "AB12CD", "Test Address", mock_epc_client) spatial1 = pd.DataFrame([{ 'X_COORDINATE': 411143.0, 'Y_COORDINATE': 281701.0, 'LATITUDE': 52.4331896, 'LONGITUDE': -1.8375238, 'conservation_status': True, 'is_listed_building': False, 'is_heritage_building': True }]) prop.set_spatial(spatial1) assert prop.in_conservation_area assert not prop.is_listed assert prop.is_heritage assert prop.restricted_measures prop2 = Property(1, "AB12CD", "Test Address", mock_epc_client) spatial2 = pd.DataFrame([{ 'X_COORDINATE': 411143.0, 'Y_COORDINATE': 281701.0, 'LATITUDE': 52.4331896, 'LONGITUDE': -1.8375238, 'conservation_status': None, 'is_listed_building': False, 'is_heritage_building': False }]) prop2.set_spatial(spatial2) assert prop2.in_conservation_area is None assert not prop2.is_listed assert not prop2.is_heritage assert not prop2.restricted_measures def test_set_floor_level(self, mock_epc_client): # In this case, we have a flat which looks looks it's on the first floor, but it's actually on the ground # floor, so we should set floor_level to 0 prop = Property(1, "AB12CD", "Test Address", mock_epc_client) prop.data = {'floor-level': '01', 'property-type': 'Flat'} prop.floor = { 'original_description': 'Solid, no insulation (assumed)', 'clean_description': 'Solid, no insulation', 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': True, 'another_property_below': False, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None, 'floor_insulation_thickness': 'none' } prop.set_floor_level() assert prop.floor_level == 0 # This property is labelled as being on the ground floor but actually has another property below # so we set floor level to 1 prop2 = Property(1, "AB12CD", "Test Address", mock_epc_client) prop2.data = {'floor-level': 'Ground', 'property-type': 'Flat'} prop2.floor = { 'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation', 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False, 'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': True, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None, 'floor_insulation_thickness': 'none' } prop2.set_floor_level() assert prop2.floor_level == 1 # this property is correctly labelled as being on the 2nd floor prop3 = Property(1, "AB12CD", "Test Address", mock_epc_client) prop3.data = {'floor-level': '02', 'property-type': 'Flat'} prop3.floor = { 'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation', 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False, 'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': True, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None, 'floor_insulation_thickness': 'none' } prop3.set_floor_level() assert prop3.floor_level == 2 # Example of a house prop4 = Property(1, "AB12CD", "Test Address", mock_epc_client) prop4.data = {'floor-level': '', 'property-type': 'House'} prop4.floor = { 'original_description': '(Another dwelling below)', 'clean_description': 'Solid, no insulation', 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_assumed': False, 'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False, 'insulation_thickness': 'none', 'floor_thermal_transmittance': None, 'floor_insulation_thickness': 'none' } prop4.set_floor_level() assert prop4.floor_level is None