"""cloud/migrate_sqlite_to_postgres.py CLI 包装层单测 (CI 第二阶段覆盖率:基线 0%)。 只测包装层本身:参数解析、行数合计打印、missing 警告分支、 unmigrated/skipped 报告行。真正的迁移逻辑在 cloud/migrations.migrate_sqlite_to_postgres,另有 test_p2_migrations / test_postgres_integration 覆盖; 本文件 patch 的是包装层调用的两个函数名,不碰迁移本身。 """ import io import sys import unittest from contextlib import redirect_stdout from pathlib import Path from unittest import mock try: from cloud import migrate_sqlite_to_postgres as mig_cli except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud import migrate_sqlite_to_postgres as mig_cli def _coverage(**over): base = {"missing_from_migration": [], "unmigrated_no_pg_schema": ["legacy_x"], "skipped_by_design": ["audit_log"]} base.update(over) return base class MigrateCliTest(unittest.TestCase): def test_totals_and_reports(self): with mock.patch.object( mig_cli, "migrate_sqlite_to_postgres", return_value={"users": 2, "tasks": 0}), \ mock.patch.object(mig_cli, "migration_coverage", return_value=_coverage()): out = io.StringIO() with redirect_stdout(out): rc = mig_cli.main(["--sqlite", "/tmp/x.db"]) self.assertEqual(rc, 0) text = out.getvalue() self.assertIn("users: 2", text) self.assertIn("tasks: 0", text) self.assertIn("total inserted: 2", text) self.assertNotIn("WARNING", text) self.assertIn("unmigrated (no PG schema, NOT migrated): legacy_x", text) self.assertIn("skipped by design: audit_log", text) def test_missing_warning_branch(self): with mock.patch.object( mig_cli, "migrate_sqlite_to_postgres", return_value={}), \ mock.patch.object( mig_cli, "migration_coverage", return_value=_coverage( missing_from_migration=["tasks"])): out = io.StringIO() with redirect_stdout(out): rc = mig_cli.main(["--sqlite", "/tmp/x.db"]) self.assertEqual(rc, 0) self.assertIn("WARNING missing_from_migration: tasks", out.getvalue()) def test_sqlite_arg_required(self): with self.assertRaises(SystemExit) as ctx: mig_cli.main([]) self.assertEqual(ctx.exception.code, 2) if __name__ == "__main__": unittest.main()