test: CREATE/DROP DATABASE через служебную БД postgres (M-9)

Команды уровня кластера в тестах (создание и удаление временных БД, роль
в тесте предвыданной схемы) подключались к рабочей БД armka. Теперь —
к служебной БД postgres того же сервера (service_url): тестам не нужна
существующая armka. Проверено прогоном с несуществующей БД в
OPENBAO__DATABASE__DATABASE_URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-04 01:55:22 +04:00
parent 87816ef6c9
commit feaac9cb9a
2 changed files with 24 additions and 7 deletions

View File

@ -1,7 +1,9 @@
"""Помощники интеграционных тестов с PostgreSQL: временная БД внутри того же сервера, что и `OPENBAO__DATABASE`.
Фикстура `scratch_db_url` создаёт пустую БД `armcore_scratch_<hex>`, отдаёт её 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:

View File

@ -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)