diff --git a/backend/app/db/connection.py b/backend/app/db/connection.py index 74f3bd2e..f0649c71 100644 --- a/backend/app/db/connection.py +++ b/backend/app/db/connection.py @@ -3,7 +3,9 @@ from contextlib import contextmanager from backend.app.config import get_settings from sqlmodel import Session -connection_string = "postgresql+{drivername}://{username}:{password}@{server}:{port}/{dbname}" +connection_string = ( + "postgresql+{drivername}://{username}:{password}@{server}:{port}/{dbname}" +) db_string = connection_string.format( drivername="psycopg2", # You'll need to use psycopg2 driver for PostgreSQL username=get_settings().DB_USERNAME, @@ -28,7 +30,9 @@ db_engine = create_engine( def get_db_session(): if db_engine is None: - raise RuntimeError("Database is not configured. Set DATABASE_URL in environment variables.") + raise RuntimeError( + "Database is not configured. Set DATABASE_URL in environment variables." + ) return Session(db_engine) diff --git a/backend/condition/persistence/condition_postgres.py b/backend/condition/persistence/condition_postgres.py new file mode 100644 index 00000000..50b1f7be --- /dev/null +++ b/backend/condition/persistence/condition_postgres.py @@ -0,0 +1,16 @@ +from typing import List + +from backend.app.db.models.condition import PropertyConditionSurveyModel +from backend.condition.domain.property_condition_survey import PropertyConditionSurvey + + +class ConditionPostgres: + + def insert_surveys(surveys: List[PropertyConditionSurvey]) -> None: + raise NotImplementedError + + @staticmethod + def map_survey_to_model( + survey: PropertyConditionSurvey, + ) -> PropertyConditionSurveyModel: + raise NotImplementedError diff --git a/backend/condition/tests/custom_asserts.py b/backend/condition/tests/custom_asserts.py index 9e3abd7f..623dcf0c 100644 --- a/backend/condition/tests/custom_asserts.py +++ b/backend/condition/tests/custom_asserts.py @@ -1,3 +1,4 @@ +from backend.app.db.models.condition import PropertyConditionSurveyModel from backend.condition.domain.property_condition_survey import PropertyConditionSurvey @@ -72,3 +73,41 @@ class CustomAsserts: f"{actual_aspect.comments} != {expected_aspect.comments}" ) return True + + def assert_property_condition_survey_model_matches_expected( + actual_model: PropertyConditionSurveyModel, + expected: dict, + ) -> None: + assert actual_model.uprn == expected["uprn"], "UPRN differs" + assert actual_model.date == expected["date"], "Date differs" + assert actual_model.source == expected["source"], "Source differs" + + assert len(actual_model.elements) == len(expected["elements"]), ( + f"Expected {len(expected['elements'])} elements, " + f"got {len(actual_model.elements)}" + ) + + for i, (actual_element, expected_element) in enumerate( + zip(actual_model.elements, expected["elements"]) + ): + assert ( + actual_element.element_type == expected_element["element_type"] + ), f"Element[{i}].element_type differs" + assert ( + actual_element.element_instance == expected_element["element_instance"] + ), f"Element[{i}].element_instance differs" + + assert len(actual_element.aspect_conditions) == len( + expected_element["aspects"] + ), f"Element[{i}] aspect count differs" + + for j, (actual_aspect, expected_aspect) in enumerate( + zip(actual_element.aspect_conditions, expected_element["aspects"]) + ): + prefix = f"Element[{i}].Aspect[{j}]" + + for key, value in expected_aspect.items(): + assert getattr(actual_aspect, key) == value, ( + f"{prefix}.{key} differs: " + f"{getattr(actual_aspect, key)} != {value}" + ) diff --git a/backend/condition/tests/persistence/test_condition_postgres.py b/backend/condition/tests/persistence/test_condition_postgres.py new file mode 100644 index 00000000..4ed4423b --- /dev/null +++ b/backend/condition/tests/persistence/test_condition_postgres.py @@ -0,0 +1,141 @@ +import pytest +from datetime import date + +from backend.condition.persistence.condition_postgres import ConditionPostgres +from backend.condition.domain.property_condition_survey import PropertyConditionSurvey +from backend.condition.domain.element import Element +from backend.condition.domain.element_type import ElementType +from backend.condition.domain.aspect_condition import AspectCondition +from backend.condition.domain.aspect_type import AspectType +from backend.app.db.models.condition import PropertyConditionSurveyModel +from backend.condition.tests.custom_asserts import CustomAsserts + + +def test_map_survey_to_model() -> None: + # arrange + survey = PropertyConditionSurvey( + uprn=1, + elements=[ + Element( + element_type=ElementType.EXTERNAL_WINDOWS, + element_instance=1, + aspect_conditions=[ + AspectCondition( + aspect_type=AspectType.MATERIAL, + aspect_instance=1, + value="UPVC Double Glazed", + quantity=8, + install_date=None, + renewal_year=2036, + comments=None, + ), + ], + ), + Element( + element_type=ElementType.EXTERNAL_DECORATION, + element_instance=1, + aspect_conditions=[ + AspectCondition( + aspect_type=AspectType.CONDITION, + aspect_instance=1, + value="Normal", + quantity=1, + install_date=None, + renewal_year=2029, + comments=None, + ) + ], + ), + Element( + element_type=ElementType.EXTERNAL_WALL, + element_instance=1, + aspect_conditions=[ + AspectCondition( + aspect_type=AspectType.FINISH, + aspect_instance=1, + value="Pointed", + quantity=65, + install_date=None, + renewal_year=2045, + comments=None, + ), + AspectCondition( + aspect_type=AspectType.FINISH, + aspect_instance=1, + value="Pointing", + quantity=1, + install_date=None, + renewal_year=2069, + comments=None, + ), + AspectCondition( + aspect_type=AspectType.FINISH, + aspect_instance=2, + value="Tile Hung", + quantity=8, + install_date=None, + renewal_year=2049, + comments=None, + ), + ], + ), + ], + date=date(2000, 1, 1), + source="Peabody", + ) + + expected = { + "uprn": 1, + "date": date(2000, 1, 1), + "source": "Peabody", + "elements": [ + { + "element_type": ElementType.EXTERNAL_WINDOWS, + "element_instance": 1, + "aspects": [ + { + "aspect_type": AspectType.MATERIAL, + "aspect_instance": 1, + "value": "UPVC Double Glazed", + "quantity": 8, + "install_date": None, + "renewal_year": 2036, + "comments": None, + } + ], + }, + { + "element_type": ElementType.EXTERNAL_DECORATION, + "element_instance": 1, + "aspects": [ + { + "aspect_type": AspectType.CONDITION, + "aspect_instance": 1, + "value": "Normal", + "quantity": 1, + "install_date": None, + "renewal_year": 2029, + "comments": None, + } + ], + }, + { + "element_type": ElementType.EXTERNAL_WALL, + "element_instance": 1, + "aspects": [ + {"value": "Pointed"}, + {"value": "Pointing"}, + {"value": "Tile Hung"}, + ], + }, + ], + } + + # act + model: PropertyConditionSurveyModel = ConditionPostgres.map_survey_to_model(survey) + + # assert (survey level) + CustomAsserts.assert_property_condition_survey_model_matches_expected( + model, + expected, + )