feat(knowledge): auto-publish successful syncs

This commit is contained in:
2026-07-16 18:07:17 +08:00
parent 921b56cfb9
commit 9ba8fb6e0c
12 changed files with 246 additions and 296 deletions

View File

@@ -1,5 +1,8 @@
from __future__ import annotations
import asyncio
from unittest.mock import AsyncMock, patch
import pytest
from fastapi import HTTPException
from sqlalchemy import create_engine, select
@@ -9,7 +12,14 @@ from sqlalchemy.pool import StaticPool
from app.api.admin_knowledge_lifecycle import batch_update_metadata, update_metadata
from app.models import Base
from app.models.admin import Admin, Role
from app.models.knowledge import Knowledge, KnowledgeManifest, KnowledgeSourceSnapshot, KnowledgeVersion
from app.models.knowledge import (
Knowledge,
KnowledgeCard,
KnowledgeManifest,
KnowledgePublishLog,
KnowledgeSourceSnapshot,
KnowledgeVersion,
)
from app.schemas.knowledge import KnowledgeBatchMetadataUpdateRequest, KnowledgeMetadataUpdateRequest
from app.services.knowledge_agent_service import KnowledgeAgentService
from app.services.knowledge_pipeline_service import KnowledgePipelineService
@@ -82,7 +92,7 @@ def test_metadata_update_preserves_source_version_and_open_state():
original = (item.feishu_space_id, item.feishu_node_id, item.current_version_id, item.status)
update_metadata(
item.id,
KnowledgeMetadataUpdateRequest(name="新后台名称", knowledgeType="course", reviewMode="automatic"),
KnowledgeMetadataUpdateRequest(name="新后台名称", knowledgeType="course"),
db,
_admin(),
)
@@ -98,7 +108,7 @@ def test_metadata_update_requires_super_admin():
with pytest.raises(HTTPException) as exc:
update_metadata(
item.id,
KnowledgeMetadataUpdateRequest(name="新名称", knowledgeType="qa", reviewMode="manual"),
KnowledgeMetadataUpdateRequest(name="新名称", knowledgeType="qa"),
db,
_admin("operator"),
)
@@ -138,3 +148,76 @@ def test_rollback_switches_published_version_statuses():
assert item.current_version_id == target.id
assert target.status == "published"
assert old_version.status == "superseded"
def test_sync_automatically_replaces_current_version_and_keeps_rollback_history():
source_content = (
"# 一、课程说明\n这是更新后的课程内容,同步成功后应当立即生效。\n\n"
"# 二、练习一:静水流深静心\n选择舒适安全的姿势,觉察呼吸和身体,不适时立即停止。"
)
with _database() as db:
item = _knowledge(db)
previous_version_id = item.current_version_id
loader = AsyncMock(return_value=[("新源标题", source_content, None)])
with patch(
"app.services.knowledge_pipeline_service.AsyncFeishuKnowledgeService._load_documents",
loader,
):
job = asyncio.run(
KnowledgePipelineService.synchronize(
db,
item,
admin_id=1,
)
)
db.refresh(item)
current = db.get(KnowledgeVersion, item.current_version_id)
previous = db.get(KnowledgeVersion, previous_version_id)
manifest = db.scalar(select(KnowledgeManifest).where(KnowledgeManifest.version_id == current.id))
cards = db.scalars(select(KnowledgeCard).where(KnowledgeCard.version_id == current.id)).all()
publish_log = db.scalar(
select(KnowledgePublishLog).where(KnowledgePublishLog.to_version_id == current.id)
)
assert job.status == "published"
assert job.mode == "replace"
assert current.status == "published"
assert current.quality_status == "passed"
assert previous.status == "superseded"
assert item.pending_version_id is None
assert item.status == 1
assert item.migration_status == "completed"
assert manifest.confirmed == 1
assert cards and all(card.review_status == "approved" for card in cards)
assert publish_log.action == "sync_publish"
def test_sync_validation_failure_keeps_current_version_active():
with _database() as db:
item = _knowledge(db)
previous_version_id = item.current_version_id
loader = AsyncMock(return_value=[("无效源内容", "内容过短", None)])
with patch(
"app.services.knowledge_pipeline_service.AsyncFeishuKnowledgeService._load_documents",
loader,
):
with pytest.raises(ValueError, match="质检未通过"):
asyncio.run(
KnowledgePipelineService.synchronize(
db,
item,
admin_id=1,
)
)
db.refresh(item)
current = db.get(KnowledgeVersion, previous_version_id)
versions = db.scalars(
select(KnowledgeVersion).where(KnowledgeVersion.knowledge_id == item.id)
).all()
assert item.current_version_id == previous_version_id
assert current.status == "published"
assert len(versions) == 1