chore: commit remaining workspace updates

This commit is contained in:
2026-07-16 07:53:41 +03:00
parent 20105f51c0
commit 45ce585aba
10 changed files with 343 additions and 122 deletions

View File

@@ -434,7 +434,12 @@ class TestManagerLifecycleDelegates:
mgr.lifecycle.resume_task_with_password = AsyncMock()
mgr._add_log = AsyncMock()
await mgr.resume_task_with_password("t1", {"db": "pass"})
mgr.lifecycle.resume_task_with_password.assert_called_with("t1", {"db": "pass"}, add_log_callback=mgr._add_log)
mgr.lifecycle.resume_task_with_password.assert_called_with(
"t1", {"db": "pass"},
add_log_callback=mgr._add_log,
requester_user_id=None,
allow_task_override=False,
)
# ═══════════════════════════════════════════════════════════════

View File

@@ -300,9 +300,9 @@ class TestWriteBackupManifest:
zf.writestr("a.txt", "data")
archive_path = Path(fname)
manifest_path = write_backup_manifest(archive_path)
# No .tmp file should remain
tmp_files = list(archive_path.parent.glob("*.tmp"))
assert len(tmp_files) == 0
# Scope: only check for this specific manifest's .tmp file
manifest_tmp = archive_path.with_suffix(".manifest.json.tmp")
assert not manifest_tmp.exists(), f"Orphan .tmp file left: {manifest_tmp}"
manifest_path.unlink()
finally:
os.unlink(fname)

View File

@@ -0,0 +1,72 @@
# #region Test.Alembic.TaskRecordsUserId [C:3] [TYPE Module] [SEMANTICS test,alembic,migration,task,postgres]
# @BRIEF Verifies the real PostgreSQL upgrade that adds task_records.user_id.
# @RELATION BINDS_TO -> [EXT:Alembic:MigrationChain]
# @TEST_FIXTURE: migration_revisions -> INLINE_JSON
# @TEST_EDGE: missing_field -> Pre-user-id schema does not expose task_records.user_id.
# @TEST_EDGE: invalid_type -> Upgrade is addressed by a concrete Alembic revision.
# @TEST_EDGE: external_fail -> PostgreSQL DDL failures propagate instead of being masked.
# @TEST_INVARIANT: task_records_user_id_upgrade -> VERIFIED_BY: [test_user_id_migration_adds_column]
# @RATIONALE SQLite and metadata.create_all() cannot expose an unapplied PostgreSQL migration;
# an isolated Testcontainers database exercises Alembic's actual DDL path.
# @REJECTED Stamping the revision or applying raw ALTER TABLE was rejected because each can
# conceal a migration chain failure while falsely marking the schema current.
import os
from pathlib import Path
import pytest
from sqlalchemy import create_engine, inspect
# #region migration_database_url [C:1] [TYPE Function]
@pytest.fixture
def migration_database_url() -> str:
"""Provide a pristine PostgreSQL database that receives only Alembic DDL."""
from testcontainers.postgres import PostgresContainer
with PostgresContainer(
image="postgres:16-alpine",
username="test",
password="test",
dbname="test_migrations",
) as container:
yield container.get_connection_url()
# #endregion migration_database_url
# #region test_user_id_migration_adds_column [C:2] [TYPE Function] [SEMANTICS test,alembic,migration,task,postgres]
# @BRIEF Upgrades a pristine PostgreSQL schema through the task_records.user_id revision.
def test_user_id_migration_adds_column(monkeypatch: pytest.MonkeyPatch, migration_database_url: str) -> None:
"""Alembic adds user_id after the predecessor revision without runtime schema creation."""
monkeypatch.setenv("DATABASE_URL", migration_database_url)
_run_alembic_upgrade("b2a3c4d5e6f7")
engine = create_engine(migration_database_url)
try:
before_columns = {column["name"] for column in inspect(engine).get_columns("task_records")}
assert "user_id" not in before_columns
_run_alembic_upgrade("c3d4e5f6a7b8")
after_columns = {column["name"] for column in inspect(engine).get_columns("task_records")}
assert "user_id" in after_columns
finally:
engine.dispose()
# #endregion test_user_id_migration_adds_column
# #region _run_alembic_upgrade [C:1] [TYPE Function]
def _run_alembic_upgrade(revision: str) -> None:
"""Run a named revision against the DATABASE_URL injected for this test."""
from alembic.config import Config
from alembic import command
backend_dir = Path(__file__).parents[2]
config = Config(str(backend_dir / "alembic.ini"))
config.set_main_option("script_location", str(backend_dir / "alembic"))
config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"])
command.upgrade(config, revision)
# #endregion _run_alembic_upgrade
# #endregion Test.Alembic.TaskRecordsUserId

View File

@@ -1,6 +1,6 @@
# #region TestAlembicMigrations [C:3] [TYPE Module] [SEMANTICS tests, alembic, migration, schema, idempotent]
# @BRIEF Verifies Alembic migration chain: fresh upgrade and legacy upgrade.
# @RELATION BINDS_TO -> [ed310b33f02c]
# #region Test.Alembic.Migrations [C:3] [TYPE Module] [SEMANTICS test,alembic,migration,schema,postgres]
# @BRIEF Verifies fresh, legacy, and targeted PostgreSQL Alembic upgrades.
# @RELATION BINDS_TO -> [Alembic.AddDeploymentRecords]
# @INVARIANT Running `alembic upgrade head` twice is idempotent (no-op on second run) on PostgreSQL.
# @INVARIANT Legacy database with existing tables can be upgraded via `alembic upgrade head`.
# @PRE Alembic is installed, migration files exist in alembic/versions/.
@@ -14,10 +14,9 @@
# for legacy databases, ensuring all missing tables/columns are created.
import os
import sys
from pathlib import Path
import pytest
import sys
# Ensure backend/src is importable for model metadata
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
@@ -38,9 +37,7 @@ _REQUIRES_PG = pytest.mark.skipif(
@_REQUIRES_PG
def test_fresh_upgrade_creates_tables() -> None:
"""Fresh Alembic upgrade creates all expected tables (PostgreSQL only)."""
from sqlalchemy import create_engine, inspect
from sqlalchemy import text as sa_text
backend_dir = Path(__file__).parent.parent
from sqlalchemy import create_engine, inspect, text as sa_text
_run_alembic_upgrade()
engine = create_engine(os.environ["DATABASE_URL"])
inspector = inspect(engine)
@@ -73,11 +70,9 @@ def test_fresh_upgrade_creates_tables() -> None:
@_REQUIRES_PG
def test_legacy_database_upgrade() -> None:
"""Alembic upgrade head works on a database that already has tables but no alembic_version."""
from sqlalchemy import create_engine, inspect
from sqlalchemy import text as sa_text
from src.models.mapping import Base
from src.models import auth as _auth # noqa: F401
from src.models import task as _task # noqa: F401
from sqlalchemy import create_engine, inspect, text as sa_text
from src.models import auth as _auth, task as _task # noqa: F401
engine = create_engine(os.environ["DATABASE_URL"])
# Drop alembic_version if exists to simulate legacy state
@@ -106,14 +101,16 @@ def test_legacy_database_upgrade() -> None:
# #region _run_alembic_upgrade [C:1] [TYPE Function]
def _run_alembic_upgrade() -> None:
"""Run alembic upgrade head programmatically."""
def _run_alembic_upgrade(revision: str = "head") -> None:
"""Run a named Alembic upgrade programmatically against DATABASE_URL."""
from alembic.config import Config as AlembicConfig
from alembic import command as alembic_command
backend_dir = Path(__file__).parent.parent
alembic_cfg = AlembicConfig(str(backend_dir / "alembic.ini"))
alembic_cfg.set_main_option("script_location", str(backend_dir / "alembic"))
alembic_command.upgrade(alembic_cfg, "head")
alembic_cfg.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"])
alembic_command.upgrade(alembic_cfg, revision)
# #endregion _run_alembic_upgrade
# #endregion TestAlembicMigrations