feat(templates): F3b 模板库 repo + 端点(GET/POST/DELETE /templates)
- TemplateRepo/SqlTemplateRepo(list/create/delete,owner stub 过滤,只 flush 端点 commit) - schemas/templates.py(title/body min_length=1→422)+ routers/templates.py(删不存在→404)+ main 注册 - 单测:repo CRUD/owner 隔离/frozen + 端点 201/列出/204/422×2/404
This commit is contained in:
104
packages/core/tests/test_template_repo.py
Normal file
104
packages/core/tests/test_template_repo.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""F3b 模板库 repo 单测(契约 §F3)。
|
||||
|
||||
`TemplateRepo` = 单用户提示词模板的 list/create/delete,统一按 `owner_id` 过滤
|
||||
(单用户原型 stub)。`create`/`delete` 只 `flush()` 不 `commit()`——提交交端点事务
|
||||
(与项目其它写侧 repo 一致,见 memory/gotchas)。`delete` 返回是否删到行(端点据此 404)。
|
||||
纯内存 fake,无 DB。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from ww_core.domain.template_repo import TemplateCreate, TemplateRepo, TemplateView
|
||||
|
||||
OWNER = uuid.UUID(int=1)
|
||||
OTHER_OWNER = uuid.UUID(int=2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeTemplateRepo:
|
||||
rows: dict[uuid.UUID, TemplateView] = field(default_factory=dict)
|
||||
owners: dict[uuid.UUID, uuid.UUID] = field(default_factory=dict)
|
||||
flushed: int = 0
|
||||
|
||||
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
|
||||
self.owners[view.id] = owner_id
|
||||
self.flushed += 1
|
||||
return view
|
||||
|
||||
async def list_for_owner(self, owner_id: uuid.UUID) -> list[TemplateView]:
|
||||
return [v for tid, v in self.rows.items() if self.owners[tid] == owner_id]
|
||||
|
||||
async def delete(self, owner_id: uuid.UUID, template_id: uuid.UUID) -> bool:
|
||||
if self.rows.get(template_id) is None or self.owners.get(template_id) != owner_id:
|
||||
return False
|
||||
del self.rows[template_id]
|
||||
del self.owners[template_id]
|
||||
self.flushed += 1
|
||||
return True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_then_list_returns_view() -> None:
|
||||
repo: TemplateRepo = _FakeTemplateRepo()
|
||||
|
||||
view = await repo.create(
|
||||
OWNER, TemplateCreate(title="爽文开局", body="主角穿越...", category=None, tool_key=None)
|
||||
)
|
||||
listed = await repo.list_for_owner(OWNER)
|
||||
|
||||
assert view.title == "爽文开局"
|
||||
assert view.body == "主角穿越..."
|
||||
assert [v.id for v in listed] == [view.id]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_filters_by_owner() -> None:
|
||||
repo: TemplateRepo = _FakeTemplateRepo()
|
||||
await repo.create(OWNER, TemplateCreate(title="a", body="b"))
|
||||
|
||||
assert await repo.list_for_owner(OTHER_OWNER) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_existing_returns_true() -> None:
|
||||
repo: TemplateRepo = _FakeTemplateRepo()
|
||||
view = await repo.create(OWNER, TemplateCreate(title="a", body="b"))
|
||||
|
||||
deleted = await repo.delete(OWNER, view.id)
|
||||
|
||||
assert deleted is True
|
||||
assert await repo.list_for_owner(OWNER) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_missing_returns_false() -> None:
|
||||
repo: TemplateRepo = _FakeTemplateRepo()
|
||||
|
||||
assert await repo.delete(OWNER, uuid.uuid4()) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_other_owner_returns_false() -> None:
|
||||
repo: TemplateRepo = _FakeTemplateRepo()
|
||||
view = await repo.create(OWNER, TemplateCreate(title="a", body="b"))
|
||||
|
||||
assert await repo.delete(OTHER_OWNER, view.id) is False
|
||||
|
||||
|
||||
def test_template_view_is_frozen() -> None:
|
||||
view = TemplateView(id=uuid.uuid4(), title="a", body="b", category=None, tool_key=None)
|
||||
with pytest.raises(ValidationError):
|
||||
view.title = "x"
|
||||
Reference in New Issue
Block a user