"""Alembic 迁移测试:全新数据库 upgrade head 后应包含全部 Phase 1 表。""" from __future__ import annotations import sqlite3 from pathlib import Path from alembic import command from alembic.config import Config BACKEND_ROOT = Path(__file__).resolve().parents[1] def _alembic_config(db_path: Path) -> Config: cfg = Config(str(BACKEND_ROOT / "alembic.ini")) cfg.set_main_option("script_location", "app/infrastructure/persistence/migrations") cfg.set_main_option("sqlalchemy.url", f"sqlite:///{db_path}") return cfg def test_upgrade_head_creates_phase1_tables(tmp_path) -> None: db_path = tmp_path / "fresh.db" command.upgrade(_alembic_config(db_path), "head") con = sqlite3.connect(db_path) try: tables = { row[0] for row in con.execute("select name from sqlite_master where type='table'").fetchall() } finally: con.close() expected = { "stock", "stock_daily", "adjust_factor", "trading_calendar", "financial_indicator", "sync_log", "alembic_version", } assert expected <= tables # 关键防未来函数列存在 con = sqlite3.connect(db_path) try: fin_cols = { row[1] for row in con.execute("pragma table_info(financial_indicator)").fetchall() } daily_cols = {row[1] for row in con.execute("pragma table_info(stock_daily)").fetchall()} finally: con.close() assert {"report_date", "announce_date"} <= fin_cols assert {"symbol", "trade_date", "close"} <= daily_cols # 来源标记列(新浪校验兜底行需与 Tushare 首选行区分) assert "source" in fin_cols assert {"source", "adjust"} <= daily_cols def test_upgrade_head_idempotent(tmp_path) -> None: db_path = tmp_path / "again.db" cfg = _alembic_config(db_path) command.upgrade(cfg, "head") command.upgrade(cfg, "head") # 二次执行不报错