import pytest from psycopg.conninfo import conninfo_to_dict from app.infra.db.project_routing import ( ActiveProjectRouting, activate_project_routing, get_active_project_routing, get_project_database_name, get_project_pgconn_string, get_schema_template_database_name, get_project_template_database_name, get_project_timescale_pgconn_string, ) def _routing(project_code: str = "project_a") -> ActiveProjectRouting: return ActiveProjectRouting( project_code=project_code, business_dsn=( "postgresql://biz_user:biz_password@biz.example:5432/biz_database" "?sslmode=require" ), timescale_dsn=( "postgresql://ts_user:ts_password@timescale.example:5433/ts_database" "?sslmode=require" ), ) def test_project_database_uses_exact_routing_dsn_for_project_code() -> None: routing = _routing() with activate_project_routing(routing): assert get_project_pgconn_string("project_a") == routing.business_dsn assert ( get_project_timescale_pgconn_string("project_a") == routing.timescale_dsn ) def test_business_template_follows_physical_project_and_timescale_uses_routing( monkeypatch, ) -> None: monkeypatch.setattr( "app.infra.db.project_routing.settings.WNDB_SCHEMA_TEMPLATE_DB_NAME", "tjwater_v2_schema_template", ) with activate_project_routing(_routing()): template_name = get_project_template_database_name("project_a") business = conninfo_to_dict(get_project_pgconn_string(template_name)) timescale = conninfo_to_dict( get_project_timescale_pgconn_string("temporary_scheme") ) assert business == { "user": "biz_user", "password": "biz_password", "dbname": "biz_database_template", "host": "biz.example", "port": "5432", "sslmode": "require", } assert timescale == { "user": "ts_user", "password": "ts_password", "dbname": "ts_database", "host": "timescale.example", "port": "5433", "sslmode": "require", } def test_project_code_resolves_to_physical_business_database(monkeypatch) -> None: monkeypatch.setattr( "app.infra.db.project_routing.settings.WNDB_SCHEMA_TEMPLATE_DB_NAME", "tjwater_v2_schema_template", ) with activate_project_routing(_routing()): assert get_project_database_name("project_a") == "biz_database" assert get_project_database_name("temporary_run") == "temporary_run" assert ( get_project_template_database_name("project_a") == "biz_database_template" ) def test_project_template_uses_database_name_outside_project_routing() -> None: assert ( get_project_template_database_name("physical_project") == "physical_project_template" ) def test_schema_template_comes_from_config(monkeypatch) -> None: monkeypatch.setattr( "app.infra.db.project_routing.settings.WNDB_SCHEMA_TEMPLATE_DB_NAME", "tjwater_v2_schema_template", ) assert get_schema_template_database_name() == "tjwater_v2_schema_template" def test_project_routing_is_nested_and_request_local() -> None: first = _routing("project_a") second = _routing("project_b") assert get_active_project_routing() is None with activate_project_routing(first): assert get_active_project_routing() is first with activate_project_routing(second): assert get_active_project_routing() is second assert get_active_project_routing() is first assert get_active_project_routing() is None def test_timescale_access_requires_iot_routing_in_project_request() -> None: business_only = _routing() business_only = ActiveProjectRouting( project_code=business_only.project_code, business_dsn=business_only.business_dsn, ) with activate_project_routing(business_only): with pytest.raises(RuntimeError, match="TimescaleDB routing is not configured"): get_project_timescale_pgconn_string()