"""Behaviour of the Postgres-backed ScenarioRepository: reading the Scenarios the Modelling stage scores a Property against, off the live ``scenario`` table. The FE creates a Scenario in the scenario-builder and passes its id to the pipeline (#1130); the orchestrator reads it back here at modelling time. Only the fields modelling uses are mapped — goal / goal_value / budget / is_default; the legacy file-path columns are ignored. See CONTEXT.md (Scenario) and ADR-0017. """ from __future__ import annotations import pytest from sqlalchemy import Engine from sqlmodel import Session from domain.modelling.measure_type import MeasureType from domain.modelling.portfolio_goal import PortfolioGoal from domain.modelling.scenario import Scenario from infrastructure.postgres.modelling import ScenarioModel from repositories.scenario.scenario_postgres_repository import ( ScenarioPostgresRepository, ) def test_get_many_maps_live_scenario_rows_to_domain_in_input_order( db_engine: Engine, ) -> None: # Arrange with Session(db_engine) as session: session.add( ScenarioModel( id=7, goal=PortfolioGoal.INCREASING_EPC, goal_value="C", budget=15000.0, is_default=True, ) ) session.add( ScenarioModel( id=9, goal=PortfolioGoal.INCREASING_EPC, goal_value="B", budget=None, is_default=False, ) ) session.commit() # Act with Session(db_engine) as session: scenarios: list[Scenario] = ScenarioPostgresRepository(session).get_many( [9, 7] ) # Assert — to_domain maps the PortfolioGoal enum to its value string assert [s.id for s in scenarios] == [9, 7] # input order preserved assert scenarios[0] == Scenario( id=9, goal="Increasing EPC", goal_value="B", budget=None, is_default=False ) assert scenarios[1] == Scenario( id=7, goal="Increasing EPC", goal_value="C", budget=15000.0, is_default=True, ) def test_get_many_parses_the_exclusions_array_into_measure_types( db_engine: Engine, ) -> None: # Arrange — the live `exclusions` column is a Postgres text-array literal of # exact MeasureType values. with Session(db_engine) as session: session.add( ScenarioModel( id=7, goal=PortfolioGoal.INCREASING_EPC, goal_value="C", is_default=True, exclusions="{solar_pv,internal_wall_insulation}", ) ) session.commit() # Act with Session(db_engine) as session: scenario: Scenario = ScenarioPostgresRepository(session).get_many([7])[0] # Assert assert scenario.exclusions == frozenset( {MeasureType.SOLAR_PV, MeasureType.INTERNAL_WALL_INSULATION} ) def test_get_many_treats_a_null_exclusions_column_as_no_exclusions( db_engine: Engine, ) -> None: # Arrange with Session(db_engine) as session: session.add( ScenarioModel( id=7, goal=PortfolioGoal.INCREASING_EPC, goal_value="C", is_default=True, exclusions=None, ) ) session.commit() # Act with Session(db_engine) as session: scenario: Scenario = ScenarioPostgresRepository(session).get_many([7])[0] # Assert assert scenario.exclusions == frozenset() def test_get_many_raises_on_an_exclusion_that_is_not_a_measure_type( db_engine: Engine, ) -> None: # Arrange — a legacy high-level category (`heating`) is not an exact # MeasureType value; exact-only resolution must reject it loudly. with Session(db_engine) as session: session.add( ScenarioModel( id=7, goal=PortfolioGoal.INCREASING_EPC, goal_value="C", is_default=True, exclusions="{heating}", ) ) session.commit() # Act / Assert with Session(db_engine) as session: with pytest.raises(ValueError): ScenarioPostgresRepository(session).get_many([7]) def test_get_many_raises_when_a_scenario_id_is_missing(db_engine: Engine) -> None: # Arrange with Session(db_engine) as session: session.add( ScenarioModel( id=7, goal=PortfolioGoal.INCREASING_EPC, goal_value="C", is_default=True, ) ) session.commit() # Act / Assert with Session(db_engine) as session: with pytest.raises(ValueError): ScenarioPostgresRepository(session).get_many([7, 404])