import asyncio import json import os import subprocess import sys from uuid import uuid4 import pytest from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine from app.db import engine as configured_engine @pytest.mark.skipif(not os.getenv("TEST_POSTGRES"), reason="Set TEST_POSTGRES=1 for isolated database migration test") def test_upgrade_existing_versions_and_downgrade(): async def check(): name = "migration_test_" + uuid4().hex admin = create_async_engine(configured_engine.url, isolation_level="AUTOCOMMIT") test_url = configured_engine.url.set(database=name) env = {**os.environ, "DATABASE_URL": test_url.render_as_string(hide_password=False)} async def alembic(*args): proc = await asyncio.to_thread(subprocess.run, [sys.executable, "-m", "alembic", *args], env=env, capture_output=True, text=True) assert proc.returncode == 0, proc.stderr async with admin.connect() as conn: await conn.execute(text(f'CREATE DATABASE "{name}"')) test_engine = create_async_engine(test_url) try: await alembic("upgrade", "0002") async with test_engine.begin() as conn: await conn.execute(text("INSERT INTO games(id,title,description,tags,engine) VALUES(1,'Old','','{}','renpy')")) await conn.execute(text("INSERT INTO game_versions(id,game_id,name,description,status,progress,size_bytes,manifest) VALUES(1,1,'Original','','ready','',0,CAST(:m AS jsonb)),(2,1,'Legacy mod','','ready','',0,CAST(:m AS jsonb))"), {"m": json.dumps({"format": "pgvn/1", "files": {}})}) await conn.execute(text("UPDATE game_versions SET parent_id=1,kind='mod' WHERE id=2")) await alembic("upgrade", "head") async with test_engine.connect() as conn: rows = (await conn.execute(text("SELECT revision_id,base_revision_id FROM game_versions"))).all() assert len(rows) == 2 and all(r[0] and r[1] is None for r in rows) assert await conn.scalar(text("SELECT count(*) FROM revisions")) == 2 await alembic("downgrade", "0002") await alembic("upgrade", "head") finally: await test_engine.dispose() async with admin.connect() as conn: await conn.execute(text(f'DROP DATABASE "{name}" WITH (FORCE)')) await admin.dispose() asyncio.run(check())