114 lines
6.9 KiB
Python
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'])
|