"""F3b GET/POST/DELETE /templates 端点(内存替身,无 DB/无网络)。 覆盖: - POST 201 + 回显 + 端点 commit;GET 列出;DELETE 204 + commit; - 空 title → 422、空 body → 422(schema `min_length=1`,FastAPI 422); - 删不存在 → 404(repo.delete 返 False → AppError NOT_FOUND)。 """ from __future__ import annotations import os import uuid import httpx import pytest from cryptography.fernet import Fernet from fakes_projects import FakeSession from ww_core.domain.template_repo import TemplateCreate, TemplateView class _FakeTemplateRepo: def __init__(self) -> None: self.rows: dict[uuid.UUID, TemplateView] = {} async def create(self, owner_id: uuid.UUID, data: TemplateCreate) -> TemplateView: view = TemplateView( id=uuid.uuid4(), title=data.title, body=data.body, category=data.category, tool_key=data.tool_key, ) self.rows[view.id] = view return view async def list_for_owner(self, owner_id: uuid.UUID) -> list[TemplateView]: return list(self.rows.values()) async def delete(self, owner_id: uuid.UUID, template_id: uuid.UUID) -> bool: if template_id in self.rows: del self.rows[template_id] return True return False def _make_client() -> tuple[httpx.AsyncClient, _FakeTemplateRepo, FakeSession]: os.environ.setdefault("CREDENTIAL_ENC_KEY", Fernet.generate_key().decode()) from ww_api.main import create_app from ww_api.services.project_deps import get_template_repo from ww_db import get_session repo = _FakeTemplateRepo() session = FakeSession() app = create_app() app.dependency_overrides[get_template_repo] = lambda: repo app.dependency_overrides[get_session] = lambda: session transport = httpx.ASGITransport(app=app) client = httpx.AsyncClient(transport=transport, base_url="http://test") return client, repo, session @pytest.mark.asyncio async def test_create_template_returns_201_and_commits() -> None: client, repo, session = _make_client() async with client: resp = await client.post( "/templates", json={"title": "爽文开局", "body": "主角穿越...", "tool_key": "expand"}, ) assert resp.status_code == 201 body = resp.json() assert body["title"] == "爽文开局" assert body["body"] == "主角穿越..." assert body["tool_key"] == "expand" assert session.commits == 1 assert len(repo.rows) == 1 @pytest.mark.asyncio async def test_list_templates_returns_created() -> None: client, repo, _session = _make_client() async with client: await client.post("/templates", json={"title": "a", "body": "b"}) resp = await client.get("/templates") assert resp.status_code == 200 items = resp.json() assert len(items) == 1 assert items[0]["title"] == "a" @pytest.mark.asyncio async def test_create_template_empty_title_returns_422() -> None: client, _repo, _session = _make_client() async with client: resp = await client.post("/templates", json={"title": "", "body": "b"}) assert resp.status_code == 422 @pytest.mark.asyncio async def test_create_template_empty_body_returns_422() -> None: client, _repo, _session = _make_client() async with client: resp = await client.post("/templates", json={"title": "a", "body": ""}) assert resp.status_code == 422 @pytest.mark.asyncio async def test_create_template_whitespace_only_title_returns_422() -> None: # strip 后为空 → min_length=1 不满足 → 422(防纯空白标题,审评 #2)。 client, _repo, _session = _make_client() async with client: resp = await client.post("/templates", json={"title": " ", "body": "b"}) assert resp.status_code == 422 @pytest.mark.asyncio async def test_create_template_whitespace_only_body_returns_422() -> None: client, _repo, _session = _make_client() async with client: resp = await client.post("/templates", json={"title": "a", "body": " "}) assert resp.status_code == 422 @pytest.mark.asyncio async def test_delete_template_returns_204_and_commits() -> None: client, repo, session = _make_client() async with client: created = await client.post("/templates", json={"title": "a", "body": "b"}) tid = created.json()["id"] resp = await client.delete(f"/templates/{tid}") assert resp.status_code == 204 assert session.commits == 2 assert repo.rows == {} @pytest.mark.asyncio async def test_delete_missing_template_returns_404() -> None: client, _repo, session = _make_client() async with client: resp = await client.delete(f"/templates/{uuid.uuid4()}") assert resp.status_code == 404 assert resp.json()["error"]["code"] == "NOT_FOUND" assert session.commits == 0