"""data.db fresh-clone and transaction regression tests.""" import os import shutil import unittest import uuid import config import data.db as db from exam_pkg.local_db import ExamLocalDB class DataDbTests(unittest.TestCase): def setUp(self): project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) test_root = os.path.join(project_root, ".data", "test-runs") os.makedirs(test_root, exist_ok=True) self.temp_dir = os.path.join(test_root, f"run-{uuid.uuid4().hex}") os.makedirs(self.temp_dir) self.original_paths = db.DB_PATHS self.original_is_dev = config.IS_DEV db.DB_PATHS = { "feedback": os.path.join(self.temp_dir, "prod", "feedback.db"), "knowledge": os.path.join(self.temp_dir, "prod", "knowledge.db"), "session": os.path.join(self.temp_dir, "dev", "session.db"), "exam": os.path.join(self.temp_dir, "dev", "exam.db"), } config.IS_DEV = True db._initialized = False def tearDown(self): db._initialized = False db.DB_PATHS = self.original_paths config.IS_DEV = self.original_is_dev test_root = os.path.realpath(os.path.join(os.path.dirname(self.temp_dir))) target = os.path.realpath(self.temp_dir) if os.path.commonpath([test_root, target]) != test_root: raise RuntimeError(f"拒绝清理测试目录之外的路径: {target}") shutil.rmtree(target, ignore_errors=True) def _table_names(self, db_name): with db.get_connection(db_name) as conn: rows = conn.execute( "SELECT name FROM sqlite_master WHERE type = 'table'" ).fetchall() return {row[0] for row in rows} def test_fresh_clone_initializes_all_dev_databases(self): db.init_databases() for path in db.DB_PATHS.values(): self.assertTrue(os.path.isfile(path), path) self.assertTrue({"feedbacks", "faqs", "faq_suggestions"} <= self._table_names("feedback")) self.assertTrue({"document_hashes", "document_versions", "sync_status"} <= self._table_names("knowledge")) self.assertTrue({"sessions", "messages", "audit_logs"} <= self._table_names("session")) self.assertTrue({"questions", "exams", "exam_questions"} <= self._table_names("exam")) with db.get_connection("knowledge") as conn: columns = { row[1] for row in conn.execute("PRAGMA table_info(document_versions)").fetchall() } self.assertTrue( {"content_hash", "expiry_date", "deprecated_date", "deprecated_by", "changed_sections"} <= columns ) # Initialization must remain safe when several services call it. db.init_databases() def test_connection_commits_and_rolls_back(self): with db.get_connection("session") as conn: conn.execute("CREATE TABLE tx_test (value TEXT)") conn.execute("INSERT INTO tx_test VALUES ('committed')") with self.assertRaisesRegex(RuntimeError, "rollback"): with db.get_connection("session") as conn: conn.execute("INSERT INTO tx_test VALUES ('rolled-back')") raise RuntimeError("rollback") with db.get_connection("session") as conn: values = [row[0] for row in conn.execute("SELECT value FROM tx_test").fetchall()] self.assertEqual(["committed"], values) def test_exam_creation_ignores_missing_question_ids(self): exam_db = ExamLocalDB() question_id = exam_db.add_question( {"content": "示例题", "answer": "答案", "score": 2}, source_file="example.txt", source_collection="public", ) exam = exam_db.create_exam("回归测试", [question_id, "missing-question"]) self.assertEqual(1, exam["total_count"]) self.assertEqual([question_id], exam["question_ids"]) self.assertEqual(1, len(exam_db.get_exam(exam["id"])["questions"])) if __name__ == "__main__": unittest.main()