"""话题ID扩展/回填验证,复用独立测试库连接配置。""" import os import re import unittest from pathlib import Path from urllib.parse import urlsplit @unittest.skipUnless(os.getenv("QINGHE_INTEGRITY_TEST_URL"), "未配置独立测试库") class TopicIdMigrationTest(unittest.TestCase): def setUp(self): import pymysql e=urlsplit(os.environ["QINGHE_INTEGRITY_TEST_URL"].removeprefix("jdbc:")) if not re.fullmatch(r"qinghe_integrity_test_[a-z0-9_]+", e.path.strip("/")):raise ValueError("禁止业务库") self.db=pymysql.connect(host=e.hostname,port=e.port or 3306,database=e.path.strip("/"), user=os.environ["QINGHE_INTEGRITY_TEST_USER"],password=os.environ["QINGHE_INTEGRITY_TEST_PASSWORD"],charset="utf8mb4") self.addCleanup(self.db.close) self.tables=["topic_follow","topic_favorite","topic_visit","topic_hot_snapshot"] with self.db.cursor() as q: for t in self.tables+ [t+"_bak_v24_id" for t in self.tables]+["topic"]:q.execute("DROP TABLE IF EXISTS `"+t+"`") q.execute("CREATE TABLE topic(id VARCHAR(36) PRIMARY KEY,name VARCHAR(100) UNIQUE,created_at DATETIME,deleted TINYINT) ENGINE=InnoDB COLLATE=utf8mb4_unicode_ci") q.execute("INSERT INTO topic VALUES('old-id','旧名_deleted_old', '2026-01-01',1),('new-id','旧名','2026-10-01',0)") for t in self.tables: q.execute("CREATE TABLE `"+t+"` (id VARCHAR(36) PRIMARY KEY,user_id VARCHAR(36),topic_name VARCHAR(100),created_at DATETIME,captured_at DATETIME) ENGINE=InnoDB") q.execute("INSERT INTO `"+t+"`(id,user_id,topic_name,created_at) VALUES('valid','u1','旧名','2026-10-02'),('deleted-parent','u2','旧名_deleted_old','2026-02-01'),('ambiguous','u3','旧名','2026-02-01'),('missing','u4','不存在','2026-10-02')") self.db.commit() def run_script(self, file): text=(Path(__file__).resolve().parents[1]/"src/main/resources/db/migration"/file).read_text(encoding="utf-8") text="\n".join(line for line in text.splitlines() if not line.lstrip().startswith("--")) try: with self.db.cursor() as q: for s in text.split(";"): if s.strip():q.execute(s) except Exception:self.db.rollback();raise def test_expand_preview_apply_repeat_and_ambiguous_rows(self): self.run_script("V23__expand_topic_stable_id.sql") self.run_script("V23__expand_topic_stable_id.sql") self.run_script("V24__backfill_topic_stable_id.sql") with self.db.cursor() as q: q.execute("SELECT COUNT(*) FROM topic_follow WHERE topic_id IS NOT NULL");self.assertEqual(0,q.fetchone()[0]) q.execute("SET @topic_id_apply=1") self.run_script("V24__backfill_topic_stable_id.sql") with self.db.cursor() as q: for t in self.tables: q.execute("SELECT id,topic_id FROM `"+t+"`");rows=dict(q.fetchall()) self.assertEqual("new-id",rows["valid"]);self.assertEqual("old-id",rows["deleted-parent"]) self.assertIsNone(rows["ambiguous"]);self.assertIsNone(rows["missing"]) q.execute("SET @topic_id_apply=1") self.run_script("V24__backfill_topic_stable_id.sql") with self.db.cursor() as q: q.execute("SELECT COUNT(*) FROM topic_follow_bak_v24_id");self.assertEqual(2,q.fetchone()[0])