import importlib.util from pathlib import Path import sqlalchemy as sa def _load_migration_module(): path = ( Path(__file__).resolve().parents[1] / "alembic" / "versions" / "20260326_create_feedback_center_tables.py" ) spec = importlib.util.spec_from_file_location("feedback_center_migration", path) module = importlib.util.module_from_spec(spec) assert spec is not None assert spec.loader is not None spec.loader.exec_module(module) return module def _feedback_center_records_table(metadata: sa.MetaData) -> sa.Table: return sa.Table( "feedback_center_records", metadata, sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True), sa.Column("tenant_id", sa.BigInteger(), nullable=False), sa.Column("project_id", sa.BigInteger(), nullable=True), sa.Column("source_type", sa.VARCHAR(length=32), nullable=False), sa.Column("source_id", sa.BigInteger(), nullable=False), sa.Column("object_type", sa.VARCHAR(length=32), nullable=False), sa.Column("object_id", sa.BigInteger(), nullable=False), sa.Column("field_name", sa.VARCHAR(length=128), nullable=False), sa.Column("before_value", sa.JSON(), nullable=True), sa.Column("after_value", sa.JSON(), nullable=True), sa.Column("ai_snapshot", sa.JSON(), nullable=True), sa.Column("ai_confidence", sa.Float(), nullable=True), sa.Column("reason_candidates", sa.JSON(), nullable=True), sa.Column("reason_code", sa.VARCHAR(length=64), nullable=True), sa.Column("reason_text", sa.Text(), nullable=True), sa.Column("review_status", sa.VARCHAR(length=16), nullable=False), sa.Column("reviewed_by", sa.BigInteger(), nullable=True), sa.Column("reviewed_at", sa.TIMESTAMP(), nullable=True), sa.Column("source_title", sa.VARCHAR(length=500), nullable=True), sa.Column("object_title", sa.VARCHAR(length=500), nullable=True), sa.Column("auto_assigned", sa.Boolean(), nullable=False), ) def test_insert_record_accepts_json_payloads_for_feedback_center_backfill() -> None: migration = _load_migration_module() metadata = sa.MetaData() table = _feedback_center_records_table(metadata) engine = sa.create_engine("sqlite:///:memory:") metadata.create_all(engine) payload = { "tenant_id": 1, "project_id": None, "source_type": "qa_feedback", "source_id": 118, "object_type": "qa_record", "object_id": 230, "field_name": "qa_feedback", "before_value": {"answer": "stub answer"}, "after_value": {"rating": "positive", "helpfulness": "fully"}, "ai_snapshot": {"question": "what happened", "answer": "stub answer"}, "ai_confidence": None, "reason_candidates": [ { "code": "answer_incorrect", "label": "答案不准确", "description": "AI 输出与业务事实不一致", } ], "reason_code": "answer_incorrect", "reason_text": None, "review_status": "reviewed", "reviewed_by": 2, "reviewed_at": None, "source_title": "what happened", "object_title": "what happened", "auto_assigned": False, } with engine.begin() as conn: migration._insert_record(conn, payload) row = conn.execute( sa.select( table.c.before_value, table.c.after_value, table.c.ai_snapshot, table.c.reason_candidates, ) ).mappings().one() assert row["before_value"] == payload["before_value"] assert row["after_value"] == payload["after_value"] assert row["ai_snapshot"] == payload["ai_snapshot"] assert row["reason_candidates"] == payload["reason_candidates"]