diff --git a/persistence/README.md b/persistence/README.md index 7517be25..157edd6d 100644 --- a/persistence/README.md +++ b/persistence/README.md @@ -38,3 +38,13 @@ TEST_DB_PASSWORD=postgres-database-password TEST_DB_HOST=database-host TEST_DB_PORT=database-port ``` + +### Database Related Unit Test +Unit tests that involve database operations require a Postgres database to be set up. +The tests connect to the database server using the default postgres user (*postgres*). +NB: You can provide any credentials for the test to connect to postgres, just make sure +the credentials are set in your .env file as explained above in *Database Configuration Parameters* section + +When the tests are run, a **test_db** database is created and then the required tables for +the test. Before the tests run, the *test_db* is deleted to ensure that each test starts +on a clean slate diff --git a/persistence/base_repo.py b/persistence/base_repo.py index 08e1103a..74c7e426 100644 --- a/persistence/base_repo.py +++ b/persistence/base_repo.py @@ -13,10 +13,9 @@ from sqlalchemy.orm import Session class BaseRepo: def __init__(self, db_name, app_env='TEST'): - config = BaseConfiguration(db_name, app_env) - engine = create_engine(config.conn_string()) - self.config = config - self.session = Session(engine) + self.config = BaseConfiguration(db_name, app_env) + self.engine = create_engine(self.config.conn_string()) + self.session = Session(self.engine) def __del__(self): """ @@ -28,3 +27,4 @@ class BaseRepo: + diff --git a/persistence/db_config.py b/persistence/db_config.py index d67eacfb..9dbbac90 100644 --- a/persistence/db_config.py +++ b/persistence/db_config.py @@ -39,4 +39,7 @@ class BaseConfiguration(object): """ if self._db_pass: return f'postgresql://{self._db_user}:{self._db_pass}@{self._db_host}:{self._db_port}/{self._db_name}' - return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}' \ No newline at end of file + return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}' + + def get_db_user(self): + return self._db_user diff --git a/persistence/models/city.py b/persistence/models/city.py index 6b4502de..6c237a6e 100644 --- a/persistence/models/city.py +++ b/persistence/models/city.py @@ -31,3 +31,12 @@ class City(Base): city_version = Column(Integer, nullable=False) created = Column(DateTime, default=datetime.datetime.utcnow) + def __init__(self, city, name, srs_name, country_code, l_corner, u_corner): + self.city = city + self.name = name + self.srs_name = srs_name + self.country_code = country_code + self.lower_corner = l_corner.tolist() + self.upper_corner = u_corner.tolist() + + diff --git a/persistence/models/heat_pump_simulation.py b/persistence/models/heat_pump_simulation.py index 715c47fa..a4c55ce4 100644 --- a/persistence/models/heat_pump_simulation.py +++ b/persistence/models/heat_pump_simulation.py @@ -74,3 +74,13 @@ class HeatPumpSimulation(Base): hp_supply_temp = Column(Float, nullable=False) created = Column(DateTime, default=datetime.datetime.utcnow) + def __init__(self, city_id, hourly_elec_demand, daily_elec_demand, monthly_elec_demand, daily_fossil, monthly_fossil): + self.city_id = city_id + self.hourly_electricity_demand = hourly_elec_demand + self.daily_electricity_demand = daily_elec_demand + self.monthly_electricity_demand = monthly_elec_demand + self.daily_fossil_fuel_consumption = daily_fossil + self.monthly_fossil_fuel_consumption = monthly_fossil + + + diff --git a/persistence/repositories/city_repo.py b/persistence/repositories/city_repo.py index 4e3cb47e..db902270 100644 --- a/persistence/repositories/city_repo.py +++ b/persistence/repositories/city_repo.py @@ -9,7 +9,6 @@ from city_model_structure.city import City from persistence import BaseRepo from sqlalchemy.exc import SQLAlchemyError from sqlalchemy import select -from helpers.city_util import CityUtil from persistence.models import City as DBCity import pickle import requests @@ -22,7 +21,6 @@ class CityRepo(BaseRepo): def __init__(self, db_name, app_env): super().__init__(db_name, app_env) - self._city_util = CityUtil() def __new__(cls, db_name, app_env): """ @@ -33,17 +31,12 @@ class CityRepo(BaseRepo): return cls._instance def insert(self, city: City) -> Union[City, Dict]: - db_city = DBCity() - db_city.name = city.name + db_city = DBCity(pickle.dumps(city), city.name, city.srs_name, city.country_code, city.lower_corner, + city.upper_corner) db_city.climate_reference_city = city.climate_reference_city - db_city.srs_name = city.srs_name db_city.longitude = city.longitude db_city.latitude = city.latitude - db_city.country_code = city.country_code db_city.time_zone = city.time_zone - db_city.lower_corner = city.lower_corner.tolist() - db_city.upper_corner = city.upper_corner.tolist() - db_city.city = pickle.dumps(city) try: # Retrieve hub project latest release @@ -137,8 +130,7 @@ class CityRepo(BaseRepo): :return: a city """ try: - self.session.query(DBCity).filter(DBCity.id == city_id).delete() - self.session.commit() + self.session.query(DBCity).filter(DBCity.id == city_id).delete() + self.session.commit() except SQLAlchemyError as err: print(f'Error while fetching city: {err}') - diff --git a/persistence/repositories/heat_pump_simulation_repo.py b/persistence/repositories/heat_pump_simulation_repo.py index ac29712a..67950828 100644 --- a/persistence/repositories/heat_pump_simulation_repo.py +++ b/persistence/repositories/heat_pump_simulation_repo.py @@ -27,10 +27,10 @@ class HeatPumpSimulationRepo(BaseRepo): cls._instance = super(HeatPumpSimulationRepo, cls).__new__(cls) return cls._instance - def insert(self, hp_simulation_data: Dict, city_id: int) -> Union[HeatPumpSimulation, Dict]: + def insert(self, hp_sim_data: Dict, city_id: int) -> Union[HeatPumpSimulation, Dict]: """ Inserts the results of heat pump simulation - :param hp_simulation_data: dictionary with heatpump the simulation inputs and output + :param hp_sim_data: dictionary with heatpump the simulation inputs and output :param city_id: the city that was used in running the simulation :return: HeatPumpSimulation """ @@ -40,27 +40,25 @@ class HeatPumpSimulationRepo(BaseRepo): return {'message': 'city not found in database'} try: - hp_simulation = HeatPumpSimulation() + hp_simulation = HeatPumpSimulation(city_id, hp_sim_data["HourlyElectricityDemand"], + hp_sim_data["DailyElectricityDemand"], hp_sim_data["MonthlyElectricityDemand"], + hp_sim_data["DailyFossilFuelConsumption"], + hp_sim_data["MonthlyFossilFuelConsumption"]) hp_simulation.city_id = city_id - hp_simulation.end_year = hp_simulation_data["EndYear"] - hp_simulation.start_year = hp_simulation_data["StartYear"] - hp_simulation.max_demand_storage_hour = hp_simulation_data["HoursOfStorageAtMaxDemand"] - hp_simulation.max_hp_energy_input = hp_simulation_data["MaximumHPEnergyInput"] - hp_simulation.building_supply_temp = hp_simulation_data["BuildingSuppTemp"] - hp_simulation.temp_difference = hp_simulation_data["TemperatureDifference"] - hp_simulation.fuel_lhv = hp_simulation_data["FuelLHV"] - hp_simulation.fuel_price = hp_simulation_data["FuelPrice"] - hp_simulation.fuel_efficiency = hp_simulation_data["FuelEF"] - hp_simulation.fuel_density = hp_simulation_data["FuelDensity"] - hp_simulation.hp_supply_temp = hp_simulation_data["HPSupTemp"] - hp_simulation.daily_electricity_demand = hp_simulation_data["DailyElectricityDemand"] - hp_simulation.hourly_electricity_demand = hp_simulation_data["HourlyElectricityDemand"] - hp_simulation.monthly_electricity_demand = hp_simulation_data["MonthlyElectricityDemand"] - hp_simulation.daily_fossil_fuel_consumption = hp_simulation_data["DailyFossilFuelConsumption"] - hp_simulation.monthly_fossil_fuel_consumption = hp_simulation_data["MonthlyFossilFuelConsumption"] - hp_simulation.simulation_type = hp_simulation_data["SimulationType"] - hp_simulation.heat_pump_model = hp_simulation_data["HeatPumpModel"] - hp_simulation.heat_pump_type = hp_simulation_data["HeatPumpType"] + hp_simulation.end_year = hp_sim_data["EndYear"] + hp_simulation.start_year = hp_sim_data["StartYear"] + hp_simulation.max_demand_storage_hour = hp_sim_data["HoursOfStorageAtMaxDemand"] + hp_simulation.max_hp_energy_input = hp_sim_data["MaximumHPEnergyInput"] + hp_simulation.building_supply_temp = hp_sim_data["BuildingSuppTemp"] + hp_simulation.temp_difference = hp_sim_data["TemperatureDifference"] + hp_simulation.fuel_lhv = hp_sim_data["FuelLHV"] + hp_simulation.fuel_price = hp_sim_data["FuelPrice"] + hp_simulation.fuel_efficiency = hp_sim_data["FuelEF"] + hp_simulation.fuel_density = hp_sim_data["FuelDensity"] + hp_simulation.hp_supply_temp = hp_sim_data["HPSupTemp"] + hp_simulation.simulation_type = hp_sim_data["SimulationType"] + hp_simulation.heat_pump_model = hp_sim_data["HeatPumpModel"] + hp_simulation.heat_pump_type = hp_sim_data["HeatPumpType"] # Persist heat pump simulation data self.session.add(hp_simulation) diff --git a/requirements.txt b/requirements.txt index 6922c468..51a47411 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ scipy PyYAML pyecore==0.12.2 python-dotenv -SQLAlchemy \ No newline at end of file +SQLAlchemy diff --git a/unittests/test_db_factory.py b/unittests/test_db_factory.py index 18593d8d..c2870ef8 100644 --- a/unittests/test_db_factory.py +++ b/unittests/test_db_factory.py @@ -8,10 +8,11 @@ from unittest import TestCase from imports.geometry_factory import GeometryFactory from imports.db_factory import DBFactory from exports.db_factory import DBFactory as ExportDBFactory -from persistence.db_config import BaseConfiguration +from persistence.base_repo import BaseRepo from sqlalchemy import create_engine from persistence.models import City from pickle import loads +from sqlalchemy.exc import ProgrammingError class TestDBFactory(TestCase): @@ -19,20 +20,36 @@ class TestDBFactory(TestCase): TestDBFactory """ - def setUp(self) -> None: + @classmethod + def setUpClass(cls) -> None: """ Test setup :return: None """ - # Create test tables if they do not exit - config = BaseConfiguration(db_name='hub_test', app_env='TEST') - engine = create_engine(config.conn_string()) - City.__table__.create(bind=engine, checkfirst=True) + # Create test database + repo = BaseRepo(db_name='test_db', app_env='TEST') + eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}') + + try: + # delete test database if it exists + conn = eng.connect() + conn.execute('commit') + conn.execute('DROP DATABASE test_db') + conn.close() + except ProgrammingError as err: + print(f'Database does not exist. Nothing to delete') + + cnn = eng.connect() + cnn.execute('commit') + cnn.execute("CREATE DATABASE test_db") + cnn.close() + + City.__table__.create(bind=repo.engine, checkfirst=True) city_file = "../unittests/tests_data/C40_Final.gml" - self.city = GeometryFactory('citygml', city_file).city - self._db_factory = DBFactory(city=self.city, db_name='hub_test', app_env='TEST') - self._export_db_factory = ExportDBFactory(city=self.city, db_name='hub_test', app_env='TEST') + cls.city = GeometryFactory('citygml', city_file).city + cls._db_factory = DBFactory(city=cls.city, db_name='test_db', app_env='TEST') + cls._export_db_factory = ExportDBFactory(city=cls.city, db_name='test_db', app_env='TEST') def test_save_city(self): saved_city = self._db_factory.persist_city() @@ -71,6 +88,3 @@ class TestDBFactory(TestCase): self.assertEqual(updated_city.longitude, 1.43589) self.assertEqual(updated_city.latitude, -9.38928339) self._db_factory.delete_city(city.id) - - - diff --git a/unittests/test_heat_pump_simulation.py b/unittests/test_heat_pump_simulation.py index 572560f4..f67bb0be 100644 --- a/unittests/test_heat_pump_simulation.py +++ b/unittests/test_heat_pump_simulation.py @@ -10,12 +10,13 @@ from imports.energy_systems_factory import EnergySystemsFactory from exports.energy_systems_factory import EnergySystemsExportFactory from imports.db_factory import DBFactory from exports.db_factory import DBFactory as ExportDBFactory -from persistence.db_config import BaseConfiguration +from persistence.base_repo import BaseRepo from sqlalchemy import create_engine from persistence.models import City from persistence.models import SimulationTypes from persistence.models import HeatPumpTypes from persistence.models import HeatPumpSimulation +from sqlalchemy.exc import ProgrammingError # User defined paramenters hp_sim_data = { @@ -37,24 +38,38 @@ class TestHeatPumpSimulation(TestCase): """ Heat pump simulation test cases """ - - def setUp(self) -> None: + @classmethod + def setUpClass(cls) -> None: """ Test setup :return: None """ + repo = BaseRepo(db_name='test_db', app_env='TEST') + eng = create_engine(f'postgresql://{repo.config.get_db_user()}@/{repo.config.get_db_user()}') + + try: + conn = eng.connect() + conn.execute('commit') + conn.execute('DROP DATABASE test_db') + conn.close() + except ProgrammingError as err: + print(f'Database does not exist. Nothing to delete') + + cnn = eng.connect() + cnn.execute('commit') + cnn.execute("CREATE DATABASE test_db") + cnn.close() + # Create test tables if they do not exit - config = BaseConfiguration(db_name='hub_test', app_env='TEST') - engine = create_engine(config.conn_string()) - City.__table__.create(bind=engine, checkfirst=True) - HeatPumpSimulation.__table__.create(bind=engine, checkfirst=True) + City.__table__.create(bind=repo.engine, checkfirst=True) + HeatPumpSimulation.__table__.create(bind=repo.engine, checkfirst=True) city_file = "../unittests/tests_data/C40_Final.gml" - self._city = GeometryFactory('citygml', city_file).city - EnergySystemsFactory('air source hp', self._city).enrich() + cls._city = GeometryFactory('citygml', city_file).city + EnergySystemsFactory('air source hp', cls._city).enrich() - self._db_factory = DBFactory(city=self._city, db_name='hub_test', app_env='TEST') - self._export_db_factory = ExportDBFactory(city=self._city, db_name='hub_test', app_env='TEST') + cls._db_factory = DBFactory(city=cls._city, db_name='test_db', app_env='TEST') + cls._export_db_factory = ExportDBFactory(city=cls._city, db_name='test_db', app_env='TEST') def test_heat_pump_simulation_persistence(self): output = EnergySystemsExportFactory(city=self._city, user_input=hp_sim_data, hp_model='018', @@ -98,6 +113,6 @@ class TestHeatPumpSimulation(TestCase): self.assertEqual(hp_sim[0].simulation_type, SimulationTypes.Series) self.assertEqual(hp_sim[0].fuel_price, hp_sim_data["FuelPrice"]) self.assertEqual(hp_sim[0].hourly_electricity_demand, output["hourly_electricity_demand"]) - self._db_factory.delete_hp_simulation(hp_sim[0].id) self._db_factory.delete_city(saved_city.id) +