diff --git a/backend/app/api/knowledge.py b/backend/app/api/knowledge.py index 98eecae..c035ce5 100644 --- a/backend/app/api/knowledge.py +++ b/backend/app/api/knowledge.py @@ -328,7 +328,7 @@ async def list_knowledge( stmt = stmt.where( or_( Item.updated_at < cur.created_at, - and_(Item.updated_at == cur.created_at, Item.id < cur.item_id), + and_(Item.updated_at == cur.created_at, Item.id < cur.id), ) ) diff --git a/backend/tests/test_knowledge.py b/backend/tests/test_knowledge.py index 76cdebc..568eb87 100644 --- a/backend/tests/test_knowledge.py +++ b/backend/tests/test_knowledge.py @@ -381,3 +381,45 @@ async def test_draft_timeline_reads_like_a_story(client: AsyncClient) -> None: body = doc["body_md"] assert "待办 → 进行中" in body assert "进行中 → 已完成" in body + + +@pytest.mark.asyncio +async def test_list_pages_through_every_document(client: AsyncClient) -> None: + """翻页要能翻到底,且不重不漏。 + + 这条用例是有来历的:知识列表的游标解出来是 `Cursor(created_at, id)`, + 查询里却写成了 `cur.item_id`——属性不存在,第二页必 500。 + 库小于一页(默认 30 条)时永远走不到这条路径,所以一直没暴露, + 直到把旧备忘导进来 84 条,用户点「加载更多」就看到"服务器内部错误"。 + """ + owner = await login(client) + ws = await create_workspace(client, owner, "翻页") + titles = [f"备忘 {i:02d}" for i in range(25)] + for title in titles: + unwrap( + await client.post( + f"{V1}/workspaces/{ws['id']}/knowledge", + json={"title": title}, + headers=owner["headers"], + ) + ) + + seen: list[str] = [] + cursor: str | None = None + for _ in range(10): # 25 条 / 每页 10 条,三轮就该到底,多留几次余地 + params: dict[str, Any] = {"limit": 10} + if cursor: + params["cursor"] = cursor + page = unwrap( + await client.get( + f"{V1}/workspaces/{ws['id']}/knowledge", params=params, headers=owner["headers"] + ) + ) + seen.extend(i["title"] for i in page["items"]) + cursor = page["next_cursor"] + if not page["has_more"] or not cursor: + break + + assert cursor is None + assert sorted(seen) == titles + assert len(seen) == len(set(seen)), "翻页翻出了重复条目"