diff --git a/api/tests/pg_helpers.py b/api/tests/pg_helpers.py index 8138471..b7c3c5c 100644 --- a/api/tests/pg_helpers.py +++ b/api/tests/pg_helpers.py @@ -1,7 +1,9 @@ """Помощники интеграционных тестов с PostgreSQL: временная БД внутри того же сервера, что и `OPENBAO__DATABASE`. Фикстура `scratch_db_url` создаёт пустую БД `armcore_scratch_`, отдаёт её SQLAlchemy-URL (драйвер asyncpg) и -в конце удаляет. Рабочую БД (`armka`) тесты не трогают. Подключается плагином в `conftest.py`. +в конце удаляет. Рабочую БД (`armka`) тесты не трогают и от её существования не зависят: `CREATE/DROP DATABASE` и +прочие команды уровня кластера идут через служебную БД `postgres` того же сервера (`service_url`). Подключается +плагином в `conftest.py`. """ import asyncio @@ -25,6 +27,15 @@ def base_url() -> URL: return make_url(settings.OPENBAO.DATABASE.DATABASE_URL) +#: Служебная БД PostgreSQL, которая есть на любом сервере: к ней подключаются для команд уровня кластера. +SERVICE_DATABASE = "postgres" + + +def service_url() -> URL: + """SQLAlchemy-URL служебной БД `postgres` на сервере из настроек (те же хост, порт и учётные данные).""" + return base_url().set(database=SERVICE_DATABASE) + + def _asyncpg_dsn(url: URL) -> str: return url.set(drivername="postgresql").render_as_string(hide_password=False) @@ -54,18 +65,17 @@ async def alembic_async(url: str, *args: str) -> str: async def create_scratch_db(name: str) -> URL: """Создать пустую БД `name` на сервере рабочей БД; вернуть SQLAlchemy-URL на неё.""" - url = base_url() - conn = await asyncpg.connect(_asyncpg_dsn(url)) + conn = await asyncpg.connect(_asyncpg_dsn(service_url())) try: await conn.execute(f'CREATE DATABASE "{name}"') finally: await conn.close() - return url.set(database=name) + return base_url().set(database=name) async def drop_scratch_db(name: str) -> None: """Удалить БД `name` (закрывая оставшиеся подключения).""" - conn = await asyncpg.connect(_asyncpg_dsn(base_url())) + conn = await asyncpg.connect(_asyncpg_dsn(service_url())) try: await conn.execute(f'DROP DATABASE IF EXISTS "{name}" WITH (FORCE)') finally: diff --git a/api/tests/test_migrations.py b/api/tests/test_migrations.py index d33289f..18b1692 100644 --- a/api/tests/test_migrations.py +++ b/api/tests/test_migrations.py @@ -18,7 +18,7 @@ from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, AsyncSession, c from src.models import Base from src.models.enums import ENUMS from tests.pg_helpers import alembic_async as alembic -from tests.pg_helpers import base_url, create_scratch_db, drop_scratch_db, run_alembic +from tests.pg_helpers import base_url, create_scratch_db, drop_scratch_db, run_alembic, service_url from tests.test_models_part_01_03 import _check_related_group_member_uniqueness from tests.test_models_part_04_06 import _check_nulls_not_distinct, _make_file_with_job from tests.test_models_part_07_09 import ( @@ -263,7 +263,7 @@ async def test_upgrade_into_preprovisioned_schema_without_database_create(scratc password = uuid.uuid4().hex scratch_url = make_url(scratch_db_url) role_url = scratch_url.set(username=role, password=password).render_as_string(hide_password=False) - server = create_async_engine(base_url(), isolation_level="AUTOCOMMIT") + server = create_async_engine(service_url(), isolation_level="AUTOCOMMIT") scratch = create_async_engine(scratch_db_url, isolation_level="AUTOCOMMIT") role_created = False try: @@ -308,3 +308,10 @@ def test_offline_sql_creates_schema_before_version_table() -> None: assert schema_at < output.index("CREATE TABLE dkb.alembic_version") assert schema_at < output.index("CREATE TYPE dkb.entity_type AS ENUM ('ЮЛ', 'ИП')") assert f"INSERT INTO dkb.alembic_version (version_num) VALUES ('{HEAD}')" in output + + +def test_cluster_commands_use_service_database() -> None: + """CREATE/DROP DATABASE и роли — через служебную БД `postgres`, а не рабочую `armka` (финальное ревью M-9).""" + assert service_url().database == "postgres" + base = base_url() + assert (service_url().host, service_url().port, service_url().username) == (base.host, base.port, base.username)