Files
kefu/deploy/protocol-integration-20260916/payload/test_protocol_knowledge_integration.py
T
2026-09-21 10:34:06 +08:00

114 lines
6.9 KiB
Python

"""Synthetic integration: protocol -> gateway -> knowledge -> retained review/receipt flow."""
import io,json,os,tempfile
from pathlib import Path
from types import SimpleNamespace
from unittest import TestCase,mock
import test_knowledge as fixtures
class StandaloneAdminTest(TestCase):
setUp=fixtures.KnowledgeApiTest.setUp
tearDown=fixtures.KnowledgeApiTest.tearDown
def test_bundled_frontend_and_same_origin_api_need_no_nginx(self):
import admin_api
expected=Path(__file__).resolve().parent/'admin-web/apps/web-antd/dist'
self.assertEqual(admin_api._frontend_dist(),expected)
self.assertEqual(self.client.get('/').status_code,200)
self.assertIn('<html',self.client.get('/').text)
self.assertEqual(self.client.get('/api/health').status_code,200)
login=self.client.post('/api/auth/login',json={'username':'admin','password':'ChangedAdmin@2026'})
self.assertEqual(login.status_code,200,login.text)
response=self.client.get('/api/knowledge/overview?account_id=-1',headers=self.headers)
self.assertEqual(response.status_code,200,response.text)
response=self.client.post('/api/knowledge/search?account_id=0',headers=self.headers,json={'query':' '})
self.assertEqual(response.status_code,422)
self.assertEqual(self.client.get('/api/knowledge/review-tasks',headers=self.headers).status_code,200)
def test_frontend_cannot_serve_files_outside_dist(self):
response=self.client.get('/%2e%2e/%2e%2e/%2e%2e/%2e%2e/backend_client.py')
self.assertEqual(response.status_code,404)
def test_management_link_uses_hash_router(self):
import backend_client
with mock.patch.object(backend_client,'load_settings',return_value={'server_url':'http://127.0.0.1:8766/'}):
self.assertEqual(backend_client.knowledge_management_url(),'http://127.0.0.1:8766/#/knowledge/center')
class ProtocolKnowledgeTest(TestCase):
setUp=fixtures.KnowledgeGatewayTest.setUp
tearDown=fixtures.KnowledgeGatewayTest.tearDown
seed=fixtures.KnowledgeGatewayTest.seed
_post_then_drain=fixtures.KnowledgeGatewayTest._post_then_drain
_upstream=fixtures.KnowledgeGatewayTest._upstream
def exercise(self,risk='low',enabled=True):
import ai_chat,ai_config,backend_client
from protocol_engine import ProtocolBot,session_key
from test_native_protocol_pipeline import Database,Sender,message,ACCOUNT,CONV
item=self.seed()
if not enabled:self.store.set_enabled(self.desktop_account['tenant_id'],False,self.actor,'')
fake_db=Database();sender=Sender(fake_db)
provider=ai_chat.Provider(kind='gateway',base_url='http://synthetic-gateway/v1/answer',api_key=self.desktop_token)
sent=[];upstream=self._upstream(json.dumps({'winner':'B','score':0.9,'risk':risk}))
def model(request):
sent.append(json.loads(request.content.decode()));return upstream(request)
def urlopen(request,**kwargs):
self.assertEqual(request.get_header('Authorization'),'Bearer '+self.desktop_token)
response,_=self._post_then_drain(model,body=json.loads(request.data))
self.assertEqual(response.status_code,200,response.text)
return io.BytesIO(response.content)
def reply(*,chat_text):
return ai_chat._gateway_completion([{'role':'user','content':chat_text}],[],provider)['content']
with mock.patch('protocol_engine.application_data_dir',return_value=self.root), \
mock.patch('queue_log.application_data_dir',return_value=self.root), \
mock.patch.object(ai_config,'AI_ENABLED',True),mock.patch.object(ai_config,'AI_REVIEW_RULES',[]), \
mock.patch.object(ai_chat,'gateway_provider',return_value=provider), \
mock.patch.object(ai_chat,'get_ai_reply',side_effect=reply), \
mock.patch.object(backend_client,'device_id',return_value='desk-1'), \
mock.patch('urllib.request.urlopen',side_effect=urlopen):
bot=ProtocolBot(db=fake_db,sender=sender,identity={'accountId':ACCOUNT,'pid':1},path=self.root/'protocol-queue.json',log=lambda _:None)
bot.send_mode='auto' if risk=='high' else 'review'
bot.message_batch_window_seconds=0;bot.send_delay_seconds=0
try:
fake_db.add(message(2,text='预约挂号需要什么材料?'));bot.poll_once()
key=session_key(ACCOUNT,CONV);pending=bot._pending[key]
self.assertTrue(pending['awaiting_review']);self.assertEqual(sender.calls,[])
trace=pending['knowledge_trace']
self.assertEqual(trace['enabled'],enabled)
self.assertEqual([h['id'] for h in trace['hits']],[item['id']] if enabled else [])
self.assertEqual(pending['judge_risk'],risk)
self.assertTrue(pending['model_task_id'])
with self.db.connect() as db:
count=db.execute('SELECT COUNT(*) FROM knowledge_retrieval_log WHERE task_id=?',(pending['model_task_id'],)).fetchone()[0]
self.assertEqual(count,int(enabled))
if enabled:self.assertIn('已审核知识参考',json.dumps(sent,ensure_ascii=False))
if risk=='low':
bot.approve_pending_replies([key]);bot.poll_once()
self.assertEqual(len(sender.calls),1)
self.assertEqual(sender.calls[0][1:3],(ACCOUNT,CONV))
self.assertEqual(bot.reply_count,1)
bot.poll_once();self.assertEqual(len(sender.calls),1)
finally:bot.close()
def test_protocol_gateway_uses_knowledge_then_preserves_approval_and_receipt_dedup(self):
self.exercise()
def test_gateway_high_risk_is_retained_by_protocol_review_gate(self):
self.exercise(risk='high')
def test_disabled_knowledge_does_not_change_protocol_delivery(self):
self.exercise(enabled=False)
class DesktopKnowledgeStateTest(TestCase):
def test_knowledge_count_comes_from_cloud_instead_of_mcp_servers(self):
import backend_client
from wechat_gui_qt import MainWindow
value=lambda n:SimpleNamespace(value=lambda:n)
text=lambda s:SimpleNamespace(text=lambda:s,toPlainText=lambda:s)
page=SimpleNamespace(tool_switches=[],agent_name=text('测试客服'),persona_prompt=text('测试人设'),
max_tokens=value(512),rounds=value(5),mcp_rounds=value(2),mcp_json=text('[{},{}]'),_reply_length_index=lambda _:1)
host=SimpleNamespace(persona_page=page,_console_model_plan=lambda:{},_console_review_rules=lambda:[])
with mock.patch.object(backend_client,'load_settings',return_value={'knowledge_summary':{'published':1234,'enabled':True}}):
state=MainWindow._console_ai_state(host)
self.assertEqual(state['knowledge'],'1234');self.assertEqual(state['knowledgeCount'],'1,234');self.assertTrue(state['knowledgeConnected'])