| import pytest |
| from sqlalchemy import create_engine, event, text |
| from sqlalchemy.orm import Session |
|
|
| from landppt.database.models import Base, Project, User |
| from landppt.database.repositories import ProjectRepository |
|
|
|
|
| class _AsyncConnectionAdapter: |
| """为仓库测试提供最小异步连接接口。""" |
|
|
| def __init__(self, connection): |
| self._connection = connection |
|
|
| async def run_sync(self, fn): |
| return fn(self._connection) |
|
|
|
|
| class _AsyncSessionAdapter: |
| """用同步 Session 模拟仓库依赖的异步 Session。""" |
|
|
| def __init__(self, session: Session): |
| self._session = session |
|
|
| async def execute(self, stmt, params=None): |
| if params is None: |
| return self._session.execute(stmt) |
| return self._session.execute(stmt, params) |
|
|
| async def commit(self): |
| self._session.commit() |
|
|
| async def rollback(self): |
| self._session.rollback() |
|
|
| async def connection(self): |
| return _AsyncConnectionAdapter(self._session.connection()) |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_delete_project_also_cleans_legacy_slide_revisions(tmp_path): |
| """删除项目时应兼容清理历史 slide_revisions 表,避免外键删除失败。""" |
| db_path = tmp_path / "project_delete.sqlite3" |
| engine = create_engine(f"sqlite:///{db_path}") |
|
|
| @event.listens_for(engine, "connect") |
| def _enable_sqlite_foreign_keys(dbapi_connection, _connection_record): |
| cursor = dbapi_connection.cursor() |
| cursor.execute("PRAGMA foreign_keys=ON") |
| cursor.close() |
|
|
| try: |
| Base.metadata.create_all(engine) |
| with engine.begin() as connection: |
| connection.execute( |
| text( |
| """ |
| CREATE TABLE slide_revisions ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| project_id VARCHAR(36) NOT NULL, |
| revision_note TEXT, |
| FOREIGN KEY (project_id) REFERENCES projects (project_id) |
| ) |
| """ |
| ) |
| ) |
|
|
| with Session(engine) as sync_session: |
| sync_session.add( |
| User( |
| id=1, |
| username="tester", |
| password_hash="hash", |
| is_active=True, |
| is_admin=False, |
| ) |
| ) |
| sync_session.add( |
| Project( |
| project_id="project-1", |
| user_id=1, |
| title="测试项目", |
| scenario="demo", |
| topic="删除兼容性", |
| ) |
| ) |
| sync_session.commit() |
|
|
| sync_session.execute( |
| text( |
| """ |
| INSERT INTO slide_revisions (project_id, revision_note) |
| VALUES (:project_id, :revision_note) |
| """ |
| ), |
| {"project_id": "project-1", "revision_note": "legacy revision"}, |
| ) |
| sync_session.commit() |
|
|
| repo = ProjectRepository(_AsyncSessionAdapter(sync_session)) |
| success = await repo.delete("project-1", user_id=1) |
|
|
| assert success is True |
|
|
| project_count = sync_session.execute( |
| text("SELECT COUNT(*) FROM projects WHERE project_id = :project_id"), |
| {"project_id": "project-1"}, |
| ).scalar_one() |
| slide_revision_count = sync_session.execute( |
| text("SELECT COUNT(*) FROM slide_revisions WHERE project_id = :project_id"), |
| {"project_id": "project-1"}, |
| ).scalar_one() |
|
|
| assert project_count == 0 |
| assert slide_revision_count == 0 |
| finally: |
| engine.dispose() |
|
|