fix(knowledge): PATCH 知识时带上 doc_type 直接 500

`patch_knowledge` 把 `doc_type`/`summary` 也塞进了给 `items` 表的 UPDATE,
于是 SQLAlchemy 报 `CompileError: Unconsumed column names: doc_type`——改类型的
请求一律 500。这两个字段本来就属于 `knowledge_doc`,下面几行已有赋值,重复写
进 items 的 values 是错的。

现在 `values` 只放 items 的列(title/body_md),`doc_changed`(doc_type/summary)
单独参与"要不要推进 version"的判断,语义不变:只要这条知识的样子变了,
version 就得 +1,否则下一次保存会拿旧 version 撞 4009。

补一个用例:PATCH 同时改 title/body_md/doc_type/tag_names 应该 200,
doc_type 生效、version 前进一步。这个 bug 之前没暴露,是因为 Web 抽屉只改
标签和分类,没人用 PATCH 改过类型。
This commit is contained in:
Steven
2026-10-30 16:18:05 +08:00
parent 9a382e4e8c
commit ffae7e380f
2 changed files with 30 additions and 6 deletions
+5 -6
View File
@@ -645,20 +645,19 @@ async def patch_knowledge(
if body.doc_type is not None and body.doc_type not in DOC_TYPES: if body.doc_type is not None and body.doc_type not in DOC_TYPES:
raise AppError(ErrorCode.VALIDATION_ERROR, f"未知的知识类型:{body.doc_type}") raise AppError(ErrorCode.VALIDATION_ERROR, f"未知的知识类型:{body.doc_type}")
# 这里只放 Item 表自己的列:doc_type / summary 属于 knowledge_doc,
# 混进来会直接 CompileError: Unconsumed column names
values: dict[str, Any] = {} values: dict[str, Any] = {}
if body.title is not None: if body.title is not None:
values["title"] = body.title.strip() values["title"] = body.title.strip()
if body.body_md is not None: if body.body_md is not None:
values["body_md"] = body.body_md values["body_md"] = body.body_md
if body.summary is not None:
values["summary"] = body.summary
if body.doc_type is not None:
values["doc_type"] = body.doc_type
# 分类/标签也改的是这条知识的"样子",所以同样要推进 version: # 分类/标签/类型也改的是这条知识的"样子",所以同样要推进 version:
# 否则只改了标签的知识,下一次保存还会拿着旧 version 撞 4009。 # 否则只改了标签的知识,下一次保存还会拿着旧 version 撞 4009。
doc_changed = body.doc_type is not None or body.summary is not None
meta_changed = body.clear_category or body.category_id is not None or body.tag_names is not None meta_changed = body.clear_category or body.category_id is not None or body.tag_names is not None
if values or meta_changed: if values or doc_changed or meta_changed:
values["updated_at"] = datetime.now(timezone.utc) values["updated_at"] = datetime.now(timezone.utc)
result = await session.execute( result = await session.execute(
update(Item) update(Item)
@@ -278,3 +278,28 @@ async def test_patch_knowledge_moves_and_clears_category(client: AsyncClient) ->
) )
) )
assert cleared["category"] is None assert cleared["category"] is None
@pytest.mark.asyncio
async def test_patch_knowledge_accepts_doc_type_and_bumps_version(client: AsyncClient) -> None:
"""PATCH 只该写 items 自己的列:doc_type/summary 混进去会 CompileError(线上 500)。"""
user, ws_id = await _ws(client)
doc = await _note(client, user, ws_id, "先写结论再写过程", body="原始正文")
patched = unwrap(
await client.patch(
f"{V1}/workspaces/{ws_id}/knowledge/{doc['id']}",
json={
"version": doc["version"],
"title": "先写结论再写过程",
"body_md": "改过的正文",
"doc_type": "howto",
"tag_names": ["写法"],
},
headers=user["headers"],
)
)
assert patched["knowledge"]["doc_type"] == "howto"
assert patched["body_md"] == "改过的正文"
assert patched["version"] == doc["version"] + 1
assert {t["name"] for t in patched["tags"]} == {"写法"}