From ffae7e380f1e6aa7bf805ad84639d39e4aab40e6 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 30 Oct 2026 16:18:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(knowledge):=20PATCH=20=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E6=97=B6=E5=B8=A6=E4=B8=8A=20doc=5Ftype=20=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 改过类型。 --- backend/app/api/knowledge.py | 11 +++++----- backend/tests/test_knowledge_categories.py | 25 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/backend/app/api/knowledge.py b/backend/app/api/knowledge.py index e755c26..15d45cc 100644 --- a/backend/app/api/knowledge.py +++ b/backend/app/api/knowledge.py @@ -645,20 +645,19 @@ async def patch_knowledge( if body.doc_type is not None and body.doc_type not in DOC_TYPES: raise AppError(ErrorCode.VALIDATION_ERROR, f"未知的知识类型:{body.doc_type}") + # 这里只放 Item 表自己的列:doc_type / summary 属于 knowledge_doc, + # 混进来会直接 CompileError: Unconsumed column names values: dict[str, Any] = {} if body.title is not None: values["title"] = body.title.strip() if body.body_md is not None: 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。 + 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 - if values or meta_changed: + if values or doc_changed or meta_changed: values["updated_at"] = datetime.now(timezone.utc) result = await session.execute( update(Item) diff --git a/backend/tests/test_knowledge_categories.py b/backend/tests/test_knowledge_categories.py index c3f0bf8..e1dfa3b 100644 --- a/backend/tests/test_knowledge_categories.py +++ b/backend/tests/test_knowledge_categories.py @@ -278,3 +278,28 @@ async def test_patch_knowledge_moves_and_clears_category(client: AsyncClient) -> ) ) 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"]} == {"写法"}