from __future__ import annotations import importlib.util from pathlib import Path import sqlalchemy as sa from alembic.migration import MigrationContext from alembic.operations import Operations def _load_migration_module(): path = ( Path(__file__).resolve().parents[2] / "alembic" / "versions" / "022_add_paper_trade_reentry_gate_reset.py" ) spec = importlib.util.spec_from_file_location("migration_022", path) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def test_upgrade_grandfathers_only_preexisting_initial_stops(): migration = _load_migration_module() engine = sa.create_engine("sqlite://") with engine.begin() as connection: connection.execute( sa.text( """ CREATE TABLE paper_trades ( id INTEGER PRIMARY KEY, status VARCHAR NOT NULL, close_reason VARCHAR, closed_at DATETIME ) """ ) ) connection.execute( sa.text( """ INSERT INTO paper_trades (id, status, close_reason, closed_at) VALUES (1, 'closed', 'stop', '2026-07-01 12:00:00'), (2, 'closed', 'manual', '2026-07-02 12:00:00'), (3, 'open', NULL, NULL) """ ) ) context = MigrationContext.configure(connection) migration.op = Operations(context) migration.upgrade() historical = connection.execute( sa.text( """ SELECT closed_at, reentry_gate_failed_at, reentry_gate_requalified_at FROM paper_trades WHERE id = 1 """ ) ).one() assert historical[1] == historical[0] assert historical[2] == historical[0] unaffected = connection.execute( sa.text( """ SELECT reentry_gate_failed_at, reentry_gate_requalified_at FROM paper_trades WHERE id IN (2, 3) ORDER BY id """ ) ).all() assert unaffected == [(None, None), (None, None)] connection.execute( sa.text( """ INSERT INTO paper_trades (id, status, close_reason, closed_at) VALUES (4, 'closed', 'stop', '2026-07-18 12:00:00') """ ) ) new_stop = connection.execute( sa.text( """ SELECT reentry_gate_failed_at, reentry_gate_requalified_at FROM paper_trades WHERE id = 4 """ ) ).one() assert new_stop == (None, None)