diff --git a/backend/alembic/env.py b/backend/alembic/env.py index bfbd92f1..2905babf 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -3,7 +3,7 @@ import logging import os import sys from logging.config import fileConfig -from urllib.parse import quote_plus +from urllib.parse import quote_plus, urlparse from alembic import context from alembic.operations import ops @@ -18,9 +18,15 @@ logger = logging.getLogger(__name__) config = context.config +def _strip_env_url(raw: str) -> str: + """Quita espacios/comillas típicos de secretos CI (.env, Gitea).""" + url = raw.strip().strip('"').strip("'") + return url + + def _normalize_alembic_sqlalchemy_url(url: str) -> str: """Alembic usa el driver síncrono psycopg2; normaliza DSN típicos de app/tests.""" - url = url.strip() + url = _strip_env_url(url) if url.startswith("postgresql+asyncpg://"): return url.replace("postgresql+asyncpg://", "postgresql+psycopg2://", 1) if url.startswith("postgresql+psycopg2://"): @@ -34,11 +40,18 @@ def _normalize_alembic_sqlalchemy_url(url: str) -> str: def get_database_url(): """Obtiene la URL de la base de datos (PostgreSQL) desde variables de entorno o alembic.ini.""" - # CI / pytest: misma URL que los tests (secret TEST_DATABASE_URL) o DATABASE_URL explícita + # CI / pytest: misma URL que los tests (secret TEST_DATABASE_URL) o DATABASE_URL explícita. + # Nota: ("X") sin coma es str, no tupla; el for iteraría caracteres y jamás leería la variable. for env_key in ("TEST_DATABASE_URL"): raw = os.environ.get(env_key) if raw and raw.strip(): - return _normalize_alembic_sqlalchemy_url(raw) + normalized = _normalize_alembic_sqlalchemy_url(raw) + if not urlparse(normalized).scheme: + raise RuntimeError( + f"{env_key} no es una URL válida (falta esquema). " + "Ejemplo: postgresql://usuario:clave@host:5432/nombre_bd" + ) + return normalized # Construcción desde settings (CORE_DB_* en .env / entorno) host = settings.CORE_DB_HOST @@ -58,11 +71,11 @@ def get_database_url(): # Fallback al archivo de configuración url = config.get_main_option("sqlalchemy.url") - if not url: + if not url or "${" in url or "%(" in url: raise RuntimeError( "No se ha configurado la cadena de conexión a PostgreSQL. " - "Define TEST_DATABASE_URL o DATABASE_URL, o CORE_DB_HOST/CORE_DB_USER/CORE_DB_PASSWORD/CORE_DB_NAME, " - "o sqlalchemy.url en alembic.ini" + "Define TEST_DATABASE_URL o DATABASE_URL, o variables CORE_DB_*; " + "sqlalchemy.url en alembic.ini con placeholders ${...} no está soportado." ) return url