feat: Phase 0 — monorepo 骨架 + 全表迁移 + FastAPI/Next 骨架 + CI
- uv(workspace) + pnpm monorepo;docker-compose(pg+api+web) - SQLAlchemy 16 MVP 表 + Alembic 初版迁移(无漂移,users stub) - FastAPI 骨架:统一错误信封(带 request_id) + structlog + /jobs/:id + OpenAPI - Next.js 骨架:纸感主题 token + OpenAPI→TS 客户端代码生成(gen:api) - CI(ruff/mypy/pytest + pg service + alembic 漂移校验) - 四份设计规格(PRODUCT/UX/ARCHITECTURE/DEV_PLAN) + CLAUDE.md
This commit is contained in:
55
tests/test_jobs_integration.py
Normal file
55
tests/test_jobs_integration.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""集成测试:GET /jobs/:id 走真实 DB(无 DB 时跳过)。
|
||||
|
||||
get_sessionmaker 缓存的 async engine 绑定首个事件循环;pytest-asyncio 每个
|
||||
测试用新循环,故每个 DB 测试清缓存以在当前循环上重建 engine,并在结束 dispose。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
from ww_db import get_sessionmaker
|
||||
from ww_db.models import Job
|
||||
|
||||
|
||||
@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(text("select 1"))
|
||||
except Exception:
|
||||
pytest.skip("postgres not reachable")
|
||||
yield maker
|
||||
await maker.kw["bind"].dispose()
|
||||
get_sessionmaker.cache_clear()
|
||||
|
||||
|
||||
async def test_get_job_returns_row(
|
||||
client: httpx.AsyncClient, sm: async_sessionmaker[AsyncSession]
|
||||
) -> None:
|
||||
async with sm() as s:
|
||||
job = Job(kind="style_learn", status="running", progress=42)
|
||||
s.add(job)
|
||||
await s.commit()
|
||||
job_id = job.id
|
||||
resp = await client.get(f"/jobs/{job_id}")
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["kind"] == "style_learn"
|
||||
assert body["progress"] == 42
|
||||
|
||||
|
||||
async def test_get_missing_job_returns_envelope(
|
||||
client: httpx.AsyncClient, sm: async_sessionmaker[AsyncSession]
|
||||
) -> None:
|
||||
resp = await client.get("/jobs/00000000-0000-0000-0000-000000000000")
|
||||
assert resp.status_code == 404
|
||||
err = resp.json()["error"]
|
||||
assert err["code"] == "NOT_FOUND"
|
||||
assert err["request_id"]
|
||||
Reference in New Issue
Block a user