From f0dee64b68bbafd8c1f172c5133efb2abbb1bc09 Mon Sep 17 00:00:00 2001 From: ssb-djo Date: Wed, 24 Jun 2026 16:59:36 +0200 Subject: [PATCH] Make the nspek postgres connection host/port/database configurable set_nspek_connection hardcoded the connection target to localhost:5432/nspek, so the NaeringsspesifikasjonWindow/Tab modules could only ever reach a database literally named "nspek" on localhost. Deployments whose nspek data lives in a differently named database (or a different host/port) fail at construction with `FATAL: database "nspek" does not exist`. Add host/port/database parameters to set_nspek_connection (defaulting to the previous localhost:5432/nspek values) and thread db_host/db_port/db_name through Naeringsspesifikasjon and its Tab/Window subclasses, mirroring the existing db_user parameter. Extract the conninfo construction into a small, testable _build_nspek_conn_url helper and drop a stray print() of the connection URL. Defaults preserve current behavior; the other internal callers (nspek_controls, the __main__ demo) are unaffected. --- src/ssb_dash_framework/modules/nspek/nspek.py | 51 ++++++++++++++-- .../modules/nspek/nspek_utils.py | 32 +++++++--- tests/test_nspek_connection.py | 59 +++++++++++++++++++ 3 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 tests/test_nspek_connection.py diff --git a/src/ssb_dash_framework/modules/nspek/nspek.py b/src/ssb_dash_framework/modules/nspek/nspek.py index fd8325d0..821c0b32 100644 --- a/src/ssb_dash_framework/modules/nspek/nspek.py +++ b/src/ssb_dash_framework/modules/nspek/nspek.py @@ -954,10 +954,20 @@ class Naeringsspesifikasjon: ] ) - def __init__(self, time_units: list[str], db_user: str | None) -> None: + def __init__( + self, + time_units: list[str], + db_user: str | None, + db_host: str = "localhost", + db_port: int = 5432, + db_name: str = "nspek", + ) -> None: """Explanation of module.""" set_nspek_connection( - db_user if db_user else "strukt-naering-developers@dapla-group-sa-p-ye.iam" + db_user if db_user else "strukt-naering-developers@dapla-group-sa-p-ye.iam", + host=db_host, + port=db_port, + database=db_name, ) self.module_number = Naeringsspesifikasjon._id_number self.module_name = self.__class__.__name__ @@ -3153,16 +3163,45 @@ def validate_nspek_data_exists(orgnr, aar, alert_store, refresh_data): class NaeringsspesifikasjonTab(TabImplementation, Naeringsspesifikasjon): """NaeringsspesifikasjonTab is an implementation of the Naeringsspesifikasjon module as a tab in a Dash application.""" - def __init__(self, time_units: list[str], db_user: str | None = None) -> None: + def __init__( + self, + time_units: list[str], + db_user: str | None = None, + db_host: str = "localhost", + db_port: int = 5432, + db_name: str = "nspek", + ) -> None: """Initializes the NaeringsspesifikasjonTab class.""" - Naeringsspesifikasjon.__init__(self, time_units=time_units, db_user=db_user) + Naeringsspesifikasjon.__init__( + self, + time_units=time_units, + db_user=db_user, + db_host=db_host, + db_port=db_port, + db_name=db_name, + ) TabImplementation.__init__(self) class NaeringsspesifikasjonWindow(WindowImplementation, Naeringsspesifikasjon): """NaeringsspesifikasjonWindow is an implementation of the Naeringsspesifikasjon module as a tab in a Dash application.""" - def __init__(self, time_units: list[str], db_user: str | None = None, **kwargs: Any) -> None: + def __init__( + self, + time_units: list[str], + db_user: str | None = None, + db_host: str = "localhost", + db_port: int = 5432, + db_name: str = "nspek", + **kwargs: Any, + ) -> None: """Initializes the NaeringsspesifikasjonWindow class.""" - Naeringsspesifikasjon.__init__(self, time_units=time_units, db_user=db_user) + Naeringsspesifikasjon.__init__( + self, + time_units=time_units, + db_user=db_user, + db_host=db_host, + db_port=db_port, + db_name=db_name, + ) WindowImplementation.__init__(self, **kwargs) diff --git a/src/ssb_dash_framework/modules/nspek/nspek_utils.py b/src/ssb_dash_framework/modules/nspek/nspek_utils.py index bff7a792..fa52ece0 100644 --- a/src/ssb_dash_framework/modules/nspek/nspek_utils.py +++ b/src/ssb_dash_framework/modules/nspek/nspek_utils.py @@ -14,11 +14,32 @@ _CONNECTION_CALLABLE_NSPEK: Callable[..., Any] | None = None -def set_nspek_connection(database_user: str | None = None) -> None: - """Helper function to configure a pooled connection to a postgres database. +def _build_nspek_conn_url( + database_user: str, + host: str = "localhost", + port: int = 5432, + database: str = "nspek", +) -> str: + """Build the psycopg ``conninfo`` URL for the nspek postgres connection.""" + encoded_user = quote_plus(database_user) + return f"postgresql://{encoded_user}@{host}:{port}/{database}" + + +def set_nspek_connection( + database_user: str | None = None, + host: str = "localhost", + port: int = 5432, + database: str = "nspek", +) -> None: + """Helper function to configure a pooled connection to the nspek postgres database. Args: - database_url: Connection url for the database. Gets passed to psycopg_pool.ConnectionPool as conninfo argument. + database_user: Database user (IAM principal) to connect as. Defaults to the + ``nspek-developers`` service account. + host: Database host. Defaults to "localhost" (e.g. a local CloudSQL proxy). + port: Database port. Defaults to 5432. + database: Database name. Defaults to "nspek". Override this (and host/port as + needed) when the nspek data lives in a differently named database. """ global _IS_POOLED_NSPEK, _CONNECTION_NSPEK, _CONNECTION_CALLABLE_NSPEK @@ -26,10 +47,7 @@ def set_nspek_connection(database_user: str | None = None) -> None: database_user if database_user else "nspek-developers@dapla-group-sa-p-ye.iam" ) - encoded_user = quote_plus(DB_USER) - conn_url = f"postgresql://{encoded_user}@localhost:5432/nspek" - if DB_USER.startswith("nspek-developers"): - print(conn_url) + conn_url = _build_nspek_conn_url(DB_USER, host=host, port=port, database=database) _IS_POOLED_NSPEK = True diff --git a/tests/test_nspek_connection.py b/tests/test_nspek_connection.py new file mode 100644 index 00000000..232f284a --- /dev/null +++ b/tests/test_nspek_connection.py @@ -0,0 +1,59 @@ +"""Tests for the configurable nspek postgres connection (host/port/database).""" + +from unittest.mock import MagicMock +from unittest.mock import patch + +from ibis import BaseBackend + +from ssb_dash_framework.modules.nspek import nspek_utils + + +def test_build_nspek_conn_url_defaults() -> None: + """Defaults reproduce the previous hardcoded localhost:5432/nspek URL.""" + url = nspek_utils._build_nspek_conn_url("nspek-developers@dapla-group-sa-p-ye.iam") + assert url == ( + "postgresql://nspek-developers%40dapla-group-sa-p-ye.iam@localhost:5432/nspek" + ) + + +def test_build_nspek_conn_url_custom_target() -> None: + """Host, port and database are all overridable.""" + url = nspek_utils._build_nspek_conn_url( + "user@iam", host="10.0.0.5", port=6432, database="strukt_naering" + ) + assert url == "postgresql://user%40iam@10.0.0.5:6432/strukt_naering" + + +def test_set_nspek_connection_forwards_target_to_pool() -> None: + """set_nspek_connection builds the conninfo from host/port/database and passes it on.""" + with ( + patch.object(nspek_utils, "ConnectionPool") as pool_cls, + patch.object(nspek_utils, "Backend") as backend, + ): + # Make the post-construction validation pass without a real DB. + backend.from_connection.return_value = MagicMock(spec=BaseBackend) + nspek_utils.set_nspek_connection( + database_user="user@iam", + host="db.internal", + port=6432, + database="strukt_naering", + ) + + pool_cls.assert_called_once_with( + conninfo="postgresql://user%40iam@db.internal:6432/strukt_naering", + min_size=1, + max_size=1, + ) + + +def test_set_nspek_connection_defaults_unchanged() -> None: + """Omitting the new args keeps the original localhost:5432/nspek behavior.""" + with ( + patch.object(nspek_utils, "ConnectionPool") as pool_cls, + patch.object(nspek_utils, "Backend") as backend, + ): + backend.from_connection.return_value = MagicMock(spec=BaseBackend) + nspek_utils.set_nspek_connection(database_user="nspek-developers@x") + + _, kwargs = pool_cls.call_args + assert kwargs["conninfo"] == "postgresql://nspek-developers%40x@localhost:5432/nspek"