Files
writer-work-flow/tests/test_project_metadata_e2e.py
2026-06-29 12:06:04 +02:00

161 lines
5.4 KiB
Python

"""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)