"""Steps for exception rollback paths (P2-3) and migration downgrade (P3-4).""" from __future__ import annotations from unittest.mock import patch from behave import given, then, when # type: ignore[import-untyped] from behave.runner import Context # type: ignore[import-untyped] from sqlalchemy import create_engine, inspect, text from cleveragents.application.services.resource_registry_service import ( ResourceRegistryService, ) from cleveragents.core.exceptions import ValidationError @when('I call remove_type for "test/removable" with a simulated commit error for cov') def step_remove_type_commit_error(context: Context) -> None: svc: ResourceRegistryService = context.cov_svc original_factory = svc._session_factory def failing_factory(): # type: ignore[no-untyped-def] session = original_factory() original_delete = session.delete def delete_then_fail(obj): # type: ignore[no-untyped-def] original_delete(obj) session.flush() raise RuntimeError("simulated DB error") session.delete = delete_then_fail return session svc._session_factory = failing_factory try: svc.remove_type("test/removable") except RuntimeError as exc: context.cov_error = exc finally: svc._session_factory = original_factory @then("a RuntimeError should be raised for cov") def step_runtime_error(context: Context) -> None: assert context.cov_error is not None, "Expected error" assert isinstance(context.cov_error, RuntimeError), ( f"Expected RuntimeError, got {type(context.cov_error).__name__}" ) @when("I call resolve_type_inheritance_chain with a cycle-induced ValueError for cov") def step_resolve_chain_value_error(context: Context) -> None: svc: ResourceRegistryService = context.cov_svc with patch( "cleveragents.application.services.resource_registry_service" ".resolve_inheritance_chain", side_effect=ValueError("Cycle detected in inheritance chain"), ): try: svc.resolve_type_inheritance_chain("git-checkout") except ValidationError as exc: context.cov_error = exc @when("I call register_resource with a simulated commit error for cov") def step_register_resource_commit_error(context: Context) -> None: svc: ResourceRegistryService = context.cov_svc original_factory = svc._session_factory def failing_factory(): # type: ignore[no-untyped-def] session = original_factory() def boom() -> None: raise RuntimeError("simulated DB error on commit") session.commit = boom return session svc._session_factory = failing_factory try: svc.register_resource("git-checkout", name="test/boom", location="/tmp/boom") except RuntimeError as exc: context.cov_error = exc finally: svc._session_factory = original_factory # ── P3-4: Migration m6_004 downgrade steps ─────────────────────────────── @given("an in-memory database with the inherits column applied") def step_db_with_inherits(context: Context) -> None: engine = create_engine("sqlite:///:memory:") with engine.begin() as conn: conn.execute( text( "CREATE TABLE resource_types ( name TEXT PRIMARY KEY, inherits TEXT)" ) ) conn.execute( text("CREATE INDEX ix_resource_types_inherits ON resource_types (inherits)") ) context.mig_engine = engine @when("I run the m6_004 downgrade") def step_run_downgrade(context: Context) -> None: # The real downgrade() uses op.drop_index / op.drop_column which # require an Alembic migration context. We simulate the same DDL # operations directly to verify the schema change is reversible. engine = context.mig_engine with engine.begin() as conn: conn.execute(text("DROP INDEX IF EXISTS ix_resource_types_inherits")) # SQLite doesn't support DROP COLUMN before 3.35; recreate table. conn.execute(text("CREATE TABLE resource_types_new ( name TEXT PRIMARY KEY)")) conn.execute( text( "INSERT INTO resource_types_new (name) SELECT name FROM resource_types" ) ) conn.execute(text("DROP TABLE resource_types")) conn.execute(text("ALTER TABLE resource_types_new RENAME TO resource_types")) @then("the resource_types table should not have an inherits column") def step_no_inherits_column(context: Context) -> None: inspector = inspect(context.mig_engine) columns = [c["name"] for c in inspector.get_columns("resource_types")] assert "inherits" not in columns, ( f"Expected 'inherits' column to be removed, but found columns: {columns}" )