from __future__ import annotations from collections.abc import Callable import pytest from sqlalchemy import Engine from sqlmodel import Session from datatypes.epc.domain.epc import Epc from domain.property_baseline.property_baseline_performance import PropertyBaselinePerformance from domain.property_baseline.performance import Performance from repositories.plan.plan_repository import PlanRepository from repositories.postgres_unit_of_work import PostgresUnitOfWork from repositories.product.product_repository import ProductRepository from repositories.scenario.scenario_repository import ScenarioRepository from repositories.spatial.spatial_repository import SpatialRepository def _session_factory(db_engine: Engine) -> Callable[[], Session]: return lambda: Session(db_engine) def _baseline() -> PropertyBaselinePerformance: perf = Performance( sap_score=72, epc_band=Epc.C, co2_emissions=1.8, primary_energy_intensity=180 ) return PropertyBaselinePerformance( lodged=perf, effective=perf, rebaseline_reason="none", space_heating_kwh=5000.0, water_heating_kwh=2000.0, ) def test_committed_work_is_visible_to_a_later_unit(db_engine: Engine) -> None: # Arrange new_unit = lambda: PostgresUnitOfWork(_session_factory(db_engine)) baseline = _baseline() # Act with new_unit() as uow: uow.property_baseline.save(baseline, property_id=10) uow.commit() # Assert — a fresh unit reads back what the first one committed. with new_unit() as uow: loaded = uow.property_baseline.get_for_property(10) assert loaded == baseline def test_an_exception_in_the_block_rolls_the_batch_back(db_engine: Engine) -> None: # Arrange new_unit = lambda: PostgresUnitOfWork(_session_factory(db_engine)) # Act — a property mid-batch raises after a write but before commit. with pytest.raises(RuntimeError, match="boom"): with new_unit() as uow: uow.property_baseline.save(_baseline(), property_id=10) raise RuntimeError("boom") # Assert — nothing from the aborted batch is persisted. with new_unit() as uow: assert uow.property_baseline.get_for_property(10) is None def test_unit_exposes_the_modelling_repos_bound_to_its_session( db_engine: Engine, ) -> None: # Arrange / Act with PostgresUnitOfWork(_session_factory(db_engine)) as uow: # Assert — the Modelling stage reads Scenario + Product and writes Plan # through the same unit (ADR-0017). assert isinstance(uow.scenario, ScenarioRepository) assert isinstance(uow.product, ProductRepository) assert isinstance(uow.plan, PlanRepository) def test_unit_exposes_the_spatial_cache_repo_bound_to_its_session( db_engine: Engine, ) -> None: # Arrange / Act with PostgresUnitOfWork(_session_factory(db_engine)) as uow: # Assert — Ingestion writes the OS spatial reference cache through the # same unit it persists the EPC/solar with (ADR-0020). assert isinstance(uow.spatial, SpatialRepository) def test_leaving_the_block_without_commit_persists_nothing(db_engine: Engine) -> None: # Arrange new_unit = lambda: PostgresUnitOfWork(_session_factory(db_engine)) # Act — write but never commit. with new_unit() as uow: uow.property_baseline.save(_baseline(), property_id=10) # Assert with new_unit() as uow: assert uow.property_baseline.get_for_property(10) is None