"""Project list metadata uses real DB state for activity and pending review counts.""" from __future__ import annotations import uuid from collections.abc import AsyncIterator from datetime import UTC, datetime, timedelta import pytest from sqlalchemy import delete, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from ww_core.domain.project_repo import SqlProjectRepo from ww_db import get_sessionmaker from ww_db.models import Chapter, ChapterReview, Project, User @pytest.fixture async def sm() -> AsyncIterator[async_sessionmaker[AsyncSession]]: get_sessionmaker.cache_clear() maker = get_sessionmaker() try: async with maker() as probe: await probe.execute(select(1)) except Exception: pytest.skip("postgres not reachable") yield maker await maker.kw["bind"].dispose() get_sessionmaker.cache_clear() async def _cleanup( sm: async_sessionmaker[AsyncSession], owner_id: uuid.UUID, project_id: uuid.UUID ) -> None: async with sm() as session: await session.execute(delete(ChapterReview).where(ChapterReview.project_id == project_id)) await session.execute(delete(Chapter).where(Chapter.project_id == project_id)) await session.execute(delete(Project).where(Project.id == project_id)) await session.execute(delete(User).where(User.id == owner_id)) await session.commit() async def _project_view( sm: async_sessionmaker[AsyncSession], owner_id: uuid.UUID, project_id: uuid.UUID ): async with sm() as session: view = await SqlProjectRepo(session).get(owner_id, project_id) assert view is not None listed = await SqlProjectRepo(session).list_for_owner(owner_id) listed_view = next((item for item in listed if item.id == project_id), None) assert listed_view is not None assert listed_view.pending_review_count == view.pending_review_count assert listed_view.updated_at == view.updated_at return view async def test_project_metadata_tracks_review_and_acceptance_state( sm: async_sessionmaker[AsyncSession], ) -> None: owner_id = uuid.uuid4() project_id = uuid.uuid4() base = datetime(2026, 6, 1, tzinfo=UTC) await _cleanup(sm, owner_id, project_id) try: async with sm() as session: session.add( User( id=owner_id, email=f"{owner_id}@example.test", display_name="metadata test", ) ) await session.commit() async with sm() as session: session.add( Project( id=project_id, owner_id=owner_id, title="元数据测试", created_at=base, updated_at=base, ) ) await session.commit() async with sm() as session: session.add( Chapter( project_id=project_id, volume=1, chapter_no=1, content="草稿一", status="draft", version=1, created_at=base + timedelta(hours=1), updated_at=base + timedelta(hours=1), ) ) await session.commit() view = await _project_view(sm, owner_id, project_id) assert view.pending_review_count == 1 assert view.updated_at == base + timedelta(hours=1) async with sm() as session: session.add( ChapterReview( project_id=project_id, chapter_no=1, chapter_version=1, conflicts=[], foreshadow_sug=[], created_at=base + timedelta(hours=2), ) ) await session.commit() view = await _project_view(sm, owner_id, project_id) assert view.pending_review_count == 0 assert view.updated_at == base + timedelta(hours=2) async with sm() as session: session.add( Chapter( project_id=project_id, volume=1, chapter_no=1, content="终稿一", status="accepted", version=2, created_at=base + timedelta(hours=3), updated_at=base + timedelta(hours=3), ) ) await session.commit() view = await _project_view(sm, owner_id, project_id) assert view.pending_review_count == 0 assert view.updated_at == base + timedelta(hours=3) async with sm() as session: draft = ( await session.execute( select(Chapter).where( Chapter.project_id == project_id, Chapter.chapter_no == 1, Chapter.version == 1, ) ) ).scalar_one() draft.content = "验收后再改草稿" draft.updated_at = base + timedelta(hours=4) await session.commit() view = await _project_view(sm, owner_id, project_id) assert view.pending_review_count == 1 assert view.updated_at == base + timedelta(hours=4) finally: await _cleanup(sm, owner_id, project_id)