feat(knowledge): add batch synchronization

This commit is contained in:
2026-07-17 13:20:41 +08:00
parent dde28aba83
commit f7076569a7
6 changed files with 217 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock, patch
import pytest
@@ -9,7 +10,7 @@ from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.api.admin_knowledge_lifecycle import batch_update_metadata, update_metadata
from app.api.admin_knowledge_lifecycle import batch_sync, batch_update_metadata, update_metadata
from app.models import Base
from app.models.admin import Admin, Role
from app.models.knowledge import (
@@ -18,9 +19,14 @@ from app.models.knowledge import (
KnowledgeManifest,
KnowledgePublishLog,
KnowledgeSourceSnapshot,
KnowledgeSyncJob,
KnowledgeVersion,
)
from app.schemas.knowledge import KnowledgeBatchMetadataUpdateRequest, KnowledgeMetadataUpdateRequest
from app.schemas.knowledge import (
KnowledgeBatchMetadataUpdateRequest,
KnowledgeBatchSyncRequest,
KnowledgeMetadataUpdateRequest,
)
from app.services.knowledge_agent_service import KnowledgeAgentService
from app.services.knowledge_pipeline_service import KnowledgePipelineService
@@ -40,8 +46,8 @@ def _knowledge(db: Session, knowledge_id: int = 1) -> Knowledge:
item = Knowledge(
id=knowledge_id,
name="原名称",
feishu_space_id="space-1",
feishu_node_id="node-1",
feishu_space_id=f"space-{knowledge_id}",
feishu_node_id=f"node-{knowledge_id}",
status=1,
knowledge_type="general",
review_mode="manual",
@@ -55,7 +61,7 @@ def _knowledge(db: Session, knowledge_id: int = 1) -> Knowledge:
source_title="源标题",
source_content="源内容",
content_hash=f"hash-{knowledge_id}",
source_identifier="space-1/node-1",
source_identifier=f"space-{knowledge_id}/node-{knowledge_id}",
)
db.add(snapshot)
db.flush()
@@ -128,6 +134,47 @@ def test_batch_metadata_update_reports_missing_items():
assert db.scalar(select(Knowledge).where(Knowledge.id == item.id)).knowledge_type == "qa"
def test_batch_sync_continues_after_an_item_fails_and_reports_each_result():
with _database() as db:
first = _knowledge(db, 1)
second = _knowledge(db, 2)
successful_job = KnowledgeSyncJob(
id=101,
knowledge_id=first.id,
mode="replace",
status="published",
stage="completed",
progress=100,
requested_by=1,
created_at=datetime.now(),
)
async def synchronize(_db, knowledge, *, admin_id):
assert admin_id == 1
if knowledge.id == second.id:
raise RuntimeError("飞书接口暂时不可用")
return successful_job
with patch.object(KnowledgePipelineService, "synchronize", side_effect=synchronize) as mocked:
response = asyncio.run(
batch_sync(
KnowledgeBatchSyncRequest(knowledgeIds=[first.id, first.id, second.id, 999]),
db,
_admin(),
)
)
result = response["data"]
assert result["total"] == 3
assert result["success"] == 1
assert result["failed"] == 2
assert [item["knowledgeId"] for item in result["results"]] == [first.id, second.id, 999]
assert result["results"][0]["status"] == "published"
assert result["results"][1]["error"] == "飞书接口暂时不可用"
assert result["results"][2]["error"] == "知识库不存在"
assert mocked.call_count == 2
def test_sync_automatically_replaces_current_version_and_removes_history():
source_content = (
"# 一、课程说明\n这是更新后的课程内容,同步成功后应当立即生效。\n\n"