Files
kefu/wechat_rpa/test_knowledge.py
T
2026-09-21 10:34:06 +08:00

512 lines
28 KiB
Python

"""Knowledge lifecycle, tenant isolation, resumability and source invalidation."""
import json
import tempfile
import time
from pathlib import Path
from unittest import TestCase, mock
from fastapi.testclient import TestClient
import admin_api
import admin_backend
from archive_store import ArchiveStore
from knowledge_processing import redact
from knowledge_retriever import KnowledgeRetriever, inject_references
from knowledge_store import KnowledgeStore, day_boundary
from knowledge_worker import KnowledgeWorker
class KnowledgeTest(TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.db = admin_backend.Database(Path(self.temp.name) / 'test.db')
self.db.initialize('InitialAdmin123')
self.actor = self.db.authenticate('admin', 'InitialAdmin123')['id']
self.store = KnowledgeStore(self.db)
self.store.initialize()
self.archive = ArchiveStore(self.db)
self.env = mock.patch.dict('os.environ', {'KNOWLEDGE_QDRANT_URL': '', 'KNOWLEDGE_EMBEDDING_URL': '', 'KNOWLEDGE_EMBEDDING_MODEL': ''})
self.env.start()
def tearDown(self):
self.env.stop()
self.temp.cleanup()
def payload(self, tenant='default', messages=None, conversation='S:customer'):
contents = messages or [('inbound', '请问预约挂号需要准备什么材料?'),
('outbound', '预约挂号请先提供就诊日期和科室,再确认可预约时段。')]
return {'tenant_id': tenant, 'source_account': {'external_account_id': 'staff-01', 'display_name': '客服'},
'messages': [{'source_message_id': str(i), 'conversation': {'external_id': conversation},
'sender': {'external_id': 'staff-01' if direction == 'outbound' else 'customer-01',
'display_name': '客服' if direction == 'outbound' else '张三',
'identity_type': 'wecom_local_uid', 'scope_id': 'staff-01'},
'direction': direction, 'message_type': 'text', 'sent_at': 1724472000 + i,
'content': content} for i, (direction, content) in enumerate(contents)]}
def start_job(self, tenant='default', **options):
with self.db.connect() as db:
account = db.execute('SELECT id FROM archive_source_account WHERE tenant_id=?', (tenant,)).fetchone()[0]
return self.store.create_job(tenant, {'source_account_id': account, 'engine': 'rules',
'max_messages': 10000, 'max_model_calls': 100, 'staff_confirmed': True, **options}, self.actor, '')
def process(self, tenant='default', payload=None):
self.archive.import_messages(payload or self.payload(tenant), self.actor, '')
job = self.start_job(tenant)
worker = KnowledgeWorker(self.store)
while worker.step():
pass
return self.store.job(tenant, job['id'])
def first(self, tenant='default'):
return self.store.list_items(tenant)['items'][0]
def publish(self, item=None, tenant='default'):
item = item or self.first(tenant)
item['conditions'] = '适用于普通门诊预约咨询;具体号源以实时查询为准。'
item = self.store.save(tenant, item['id'], item, self.actor, '')
self.store.transition(tenant, item['id'], item['revision'], 'approve', self.actor, '', True)
return self.store.publish(tenant, item['id'], item['revision'], self.actor, '')
def test_lifecycle_only_published_knowledge_is_retrievable(self):
job = self.process()
self.assertEqual((job['status'], job['created_items']), ('completed', 1))
retriever = KnowledgeRetriever(self.store)
self.assertFalse(retriever.search('default', '预约挂号材料')['hits'])
item = self.publish()
hits = retriever.search('default', '预约挂号材料')['hits']
self.assertEqual(hits[0]['id'], item['id'])
self.assertEqual(len(self.store.detail('default', item['id'])['sources']), 2)
self.store.transition('default', item['id'], item['revision'], 'disable', self.actor, '')
self.assertFalse(retriever.search('default', '预约挂号材料')['hits'])
def test_edits_remove_publication_and_stale_edit_is_rejected(self):
self.process()
item = self.publish()
saved = self.store.save('default', item['id'], item, self.actor, '')
self.assertEqual(saved['status'], 'draft')
with self.assertRaises(ValueError):
self.store.save('default', item['id'], item, self.actor, '')
self.assertFalse(KnowledgeRetriever(self.store).search('default', '预约挂号')['hits'])
def test_source_revision_invalidates_knowledge_and_cached_references(self):
self.process()
item = self.publish()
self.store.set_enabled('default', True, self.actor, '')
retriever = KnowledgeRetriever(self.store)
hits = retriever.search('default', '预约挂号')['hits']
self.assertTrue(retriever.references_current('default', hits))
payload = self.payload()
payload['messages'][1]['status'] = 'revoked'
self.archive.import_messages(payload, self.actor, '')
self.assertEqual(self.first()['status'], 'stale')
self.assertFalse(retriever.search('default', '预约挂号')['hits'])
self.assertFalse(retriever.references_current('default', hits))
with self.assertRaises(ValueError):
self.store.save('default', item['id'], item, self.actor, '')
def test_reprocessing_is_idempotent(self):
self.process()
job = self.start_job()
while KnowledgeWorker(self.store).step():
pass
self.assertEqual(self.store.job('default', job['id'])['duplicates'], 1)
self.assertEqual(self.store.list_items('default')['total'], 1)
def test_tenants_cannot_read_or_publish_each_others_knowledge(self):
self.process('tenant_a')
item = self.publish(tenant='tenant_a')
self.assertEqual(self.store.list_items('tenant_b')['total'], 0)
self.assertFalse(KnowledgeRetriever(self.store).search('tenant_b', '预约挂号')['hits'])
with self.assertRaises(KeyError):
self.store.publish('tenant_b', item['id'], item['revision'], self.actor, '')
def test_cross_page_answers_are_preserved_and_pause_resumes(self):
content = [('unknown', '系统消息')] * 198 + [('inbound', '预约挂号需要什么材料?'),
('outbound', '请先确认就诊日期。'), ('outbound', '还需要确认就诊科室。')]
self.archive.import_messages(self.payload(messages=content), self.actor, '')
job = self.start_job()
worker = KnowledgeWorker(self.store)
worker.step()
self.assertEqual(self.store.list_items('default')['total'], 0)
self.store.job_action('default', job['id'], 'pause', self.actor, '')
self.assertFalse(worker.step())
self.store.job_action('default', job['id'], 'resume', self.actor, '')
while KnowledgeWorker(self.store).step():
pass
self.assertIn('确认就诊日期', self.first()['answer'])
self.assertIn('确认就诊科室', self.first()['answer'])
def test_sample_limit_does_not_publish_half_answer(self):
payload = self.payload(messages=[('inbound', '预约挂号需要什么材料?'),
('outbound', '请先确认就诊日期。'), ('outbound', '还需要确认就诊科室。')])
self.archive.import_messages(payload, self.actor, '')
self.start_job(max_messages=2)
KnowledgeWorker(self.store).step()
self.assertEqual(self.store.list_items('default')['total'], 0)
def test_snapshot_uses_old_version_and_marks_changed_source_stale(self):
self.archive.import_messages(self.payload(), self.actor, '')
self.start_job()
payload = self.payload()
payload['messages'][1]['content'] = '新的业务口径请咨询前台工作人员。'
self.archive.import_messages(payload, self.actor, '')
with self.db.connect() as db:
db.execute("UPDATE archive_message_version SET created_at='2099-01-01T00:00:00.000+00:00' WHERE version_no=2")
while KnowledgeWorker(self.store).step():
pass
item = self.first()
self.assertIn('就诊日期', item['answer'])
self.assertEqual(item['status'], 'stale')
def test_group_and_unverified_outbound_are_excluded(self):
self.process(payload=self.payload(conversation='R:group'))
self.assertEqual(self.store.list_items('default')['total'], 0)
payload = self.payload()
payload['messages'][1]['sender']['external_id'] = 'unknown-employee'
self.process(payload=payload)
self.assertEqual(self.store.list_items('default')['total'], 0)
def test_names_and_contact_details_are_redacted_before_model(self):
self.process(payload=self.payload(messages=[('inbound', '我是张三,手机号13800138000,怎么预约挂号?'),
('outbound', '张三,预约挂号请先确认就诊日期。联系13800138000。')]))
detail = self.store.detail('default', self.first()['id'])
text = json.dumps(detail, ensure_ascii=False)
self.assertNotIn('张三', text)
self.assertNotIn('13800138000', text)
self.assertIn('[姓名]', text)
def test_expired_knowledge_not_returned(self):
self.process()
item = self.first()
item['valid_until'] = '2099-01-01'
self.publish(item)
with mock.patch.object(KnowledgeRetriever, '_today', return_value='2099-01-02'):
self.assertFalse(KnowledgeRetriever(self.store).search('default', '预约挂号')['hits'])
def test_abandoned_lease_is_recovered(self):
self.archive.import_messages(self.payload(), self.actor, '')
job = self.start_job()
worker = KnowledgeWorker(self.store)
worker.claim()
self.assertFalse(KnowledgeWorker(self.store).step())
with self.db.connect() as db:
db.execute('UPDATE knowledge_job SET lease_until=0 WHERE id=?', (job['id'],))
while KnowledgeWorker(self.store).step():
pass
self.assertEqual(self.store.list_items('default')['total'], 1)
def test_vector_stale_revision_or_wrong_tenant_is_not_returned(self):
self.process()
item = self.publish()
with mock.patch('knowledge_retriever.VectorIndex') as index:
index.return_value.configured = True
index.return_value.profile = 'test'
index.return_value.search.return_value = [{'score': 0.99, 'payload': {
'tenant_id': 'default', 'item_id': item['id'], 'revision': 999}}]
self.assertFalse(KnowledgeRetriever(self.store).search('default', '完全不同的问题')['hits'])
index.return_value.search.return_value[0]['payload']['tenant_id'] = 'tenant_b'
self.assertFalse(KnowledgeRetriever(self.store).search('default', '完全不同的问题')['hits'])
def test_incremental_watch_enqueues_only_changed_conversation(self):
self.archive.import_messages(self.payload(), self.actor, '')
self.start_job(auto_watch=True)
while KnowledgeWorker(self.store).step():
pass
payload = self.payload()
payload['messages'][1]['content'] = '预约挂号请携带既往就诊记录并确认就诊日期。'
self.archive.import_messages(payload, self.actor, '')
with self.db.connect() as db:
self.assertEqual(db.execute('SELECT COUNT(*) FROM knowledge_dirty').fetchone()[0], 1)
self.assertFalse(KnowledgeWorker(self.store).step(), 'Active conversations should wait for a quiet interval')
with self.db.connect() as db:
db.execute("UPDATE knowledge_dirty SET changed_at='2000-01-01T00:00:00.000+00:00'")
while KnowledgeWorker(self.store).step():
pass
items = self.store.list_items('default')['items']
self.assertEqual(len(items), 2)
self.assertEqual({item['status'] for item in items}, {'draft', 'stale'})
self.assertEqual(len(self.store.jobs('default')), 2)
def test_publish_checks_source_again_after_vector_upload(self):
self.process()
item = self.first()
item['conditions'] = '通用预约流程'
item = self.store.save('default', item['id'], item, self.actor, '')
self.store.transition('default', item['id'], item['revision'], 'approve', self.actor, '', True)
def changed(_item):
payload = self.payload()
payload['messages'][1]['status'] = 'revoked'
self.archive.import_messages(payload, self.actor, '')
return 'profile'
with mock.patch('knowledge_retriever.VectorIndex') as index:
index.return_value.configured = True
index.return_value.publish.side_effect = changed
with self.assertRaises(ValueError):
self.store.publish('default', item['id'], item['revision'], self.actor, '')
self.assertFalse(KnowledgeRetriever(self.store).search('default', '预约挂号')['hits'])
def test_model_call_budget_is_durable_on_failure(self):
self.archive.import_messages(self.payload(), self.actor, '')
with mock.patch('knowledge_models.resolve_model', side_effect=lambda db, options: options.update(model_provider_id='test-model')):
job = self.start_job(engine='model', max_model_calls=1)
with mock.patch('knowledge_worker.enhance', side_effect=ValueError('模型整理失败')) as enhance:
KnowledgeWorker(self.store).step()
failed = self.store.job('default', job['id'])
self.assertEqual((failed['status'], failed['model_calls']), ('failed', 1))
self.store.job_action('default', job['id'], 'resume', self.actor, '')
KnowledgeWorker(self.store).step()
self.assertEqual(enhance.call_count, 1)
def test_reindex_changes_only_current_publications(self):
from knowledge_worker import reindex
self.process()
item = self.publish()
with mock.patch('knowledge_retriever.VectorIndex') as index:
index.return_value.configured = True
index.return_value.publish.return_value = 'profile-new'
self.assertEqual(reindex(self.store, 'default'), 1)
self.assertEqual(self.first()['vector_profile'], 'profile-new')
def disable(_item):
self.store.transition('default', item['id'], item['revision'], 'disable', self.actor, '')
return 'profile-after-disable'
with mock.patch('knowledge_retriever.VectorIndex') as index:
index.return_value.configured = True
index.return_value.publish.side_effect = disable
self.assertEqual(reindex(self.store, 'default'), 0)
self.assertEqual(self.first()['vector_profile'], 'profile-new')
def test_query_uses_tail_customer_bubbles_not_old_history(self):
from knowledge_processing import latest_query
chat = '客服 10:00\n旧话题\n客户 10:01\n我要预约\n客户 10:02\n需要什么材料?'
self.assertEqual(latest_query(chat), '我要预约\n需要什么材料?')
self.assertEqual(latest_query('[语音] 00:05'), '[语音] 00:05')
def test_dates_use_business_timezone_and_prompt_does_not_mutate_input(self):
self.assertEqual(day_boundary('2026-09-16'), '2026-09-15T16:00:00.000+00:00')
messages = [{'role': 'user', 'content': '怎么预约'}]
changed = inject_references(messages, [])
self.assertEqual(messages[0]['content'], '怎么预约')
self.assertIn('不要编造', changed[0]['content'])
def test_preview_is_read_only_scoped_and_counts_only_direct_conversations(self):
self.archive.import_messages(self.payload(), self.actor, '')
self.archive.import_messages(self.payload('tenant_b'), self.actor, '')
group = self.payload(conversation='R:group')
for row in group['messages']:
row['source_message_id'] = 'group-' + row['source_message_id']
row['conversation']['conversation_type'] = 'group'
self.archive.import_messages(group, self.actor, '')
with self.db.connect() as db:
source = db.execute("SELECT id FROM archive_source_account WHERE tenant_id='default'").fetchone()[0]
options = {'source_account_id': source, 'max_messages': 1}
preview = self.store.preview_job('default', options)
self.assertEqual((preview['available_messages'], preview['total_messages'], preview['conversations']), (2, 1, 1))
self.assertEqual(preview['eligible_text_messages'], 2)
self.assertTrue(preview['limited'])
self.assertEqual(self.store.jobs('default'), [])
with self.assertRaises(ValueError):
self.store.preview_job('tenant_b', options)
with self.assertRaises(ValueError):
self.store.preview_job('default', {**options, 'date_from': '2026-09-16', 'date_to': '2026-09-15'})
empty = self.store.preview_job('default', {**options, 'date_from': '2099-01-01'})
self.assertEqual(empty['total_messages'], 0)
def test_exact_limit_preserves_complete_final_answer_and_progress(self):
self.archive.import_messages(self.payload(), self.actor, '')
job = self.start_job(max_messages=2)
self.assertEqual((job['total_messages'], job['progress_percent']), (2, 0))
KnowledgeWorker(self.store).step()
finished = self.store.job('default', job['id'])
self.assertEqual((finished['processed'], finished['progress_percent'], finished['completion_reason']),
(2, 100, 'source_exhausted'))
self.assertEqual(finished['created_items'], 1)
def test_real_progress_and_limit_reason(self):
rows = [('unknown', '系统消息')] * 401
self.archive.import_messages(self.payload(messages=rows), self.actor, '')
job = self.start_job(max_messages=201)
worker = KnowledgeWorker(self.store)
worker.step()
partial = self.store.job('default', job['id'])
self.assertEqual((partial['processed'], partial['total_messages'], partial['progress_percent']), (200, 201, 99))
worker.step()
final = self.store.job('default', job['id'])
self.assertEqual((final['processed'], final['completion_reason']), (201, 'message_limit'))
def test_cancel_fences_an_inflight_batch_and_allows_replacement(self):
self.archive.import_messages(self.payload(), self.actor, '')
job = self.start_job()
with self.assertRaisesRegex(ValueError, '已有未完成任务'):
self.start_job()
worker = KnowledgeWorker(self.store)
original = worker.rows
def cancel_after_read(row, size):
rows = original(row, size)
self.store.job_action('default', job['id'], 'cancel', self.actor, '')
return rows
with mock.patch.object(worker, 'rows', side_effect=cancel_after_read):
worker.step()
self.assertEqual(self.store.job('default', job['id'])['status'], 'cancelled')
self.assertEqual(self.store.list_items('default')['total'], 0)
with self.assertRaises(ValueError):
self.store.job_action('default', job['id'], 'resume', self.actor, '')
replacement = self.start_job()
worker.step()
self.assertEqual(self.store.job('default', replacement['id'])['created_items'], 1)
def test_enabling_requires_current_unexpired_publication(self):
with self.assertRaises(ValueError):
self.store.set_enabled('default', True, self.actor, '')
self.process()
item = self.publish()
self.store.set_enabled('default', True, self.actor, '')
self.store.transition('default', item['id'], item['revision'], 'disable', self.actor, '')
self.store.set_enabled('default', False, self.actor, '')
with self.assertRaises(ValueError):
self.store.set_enabled('default', True, self.actor, '')
def test_review_and_publication_validate_expiry_and_state(self):
self.process()
item = self.first()
with self.assertRaises(ValueError):
self.store.transition('default', item['id'], item['revision'], 'disable', self.actor, '')
item['conditions'], item['valid_until'] = '通用预约流程', '2000-01-01'
item = self.store.save('default', item['id'], item, self.actor, '')
with self.assertRaisesRegex(ValueError, '有效期'):
self.store.transition('default', item['id'], item['revision'], 'approve', self.actor, '', True)
item['valid_until'] = '2099-01-01'
item = self.store.save('default', item['id'], item, self.actor, '')
self.store.transition('default', item['id'], item['revision'], 'approve', self.actor, '', True)
with mock.patch.object(KnowledgeRetriever, '_today', return_value='2099-01-02'):
with self.assertRaisesRegex(ValueError, '有效期'):
self.store.publish('default', item['id'], item['revision'], self.actor, '')
def test_preview_eligible_text_matches_worker_identity_checks(self):
payload = self.payload(messages=[('inbound', '预约挂号需要什么材料?'), ('outbound', '请先确认日期。'), ('unknown', '无法确认角色')])
payload['messages'][1]['sender']['external_id'] = 'other-person'
self.archive.import_messages(payload, self.actor, '')
with self.db.connect() as db:
source = db.execute('SELECT id FROM archive_source_account').fetchone()[0]
preview = self.store.preview_job('default', {'source_account_id': source})
self.assertEqual((preview['available_messages'], preview['eligible_text_messages']), (3, 1))
class KnowledgeApiTest(TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.db = admin_backend.Database(Path(self.temp.name) / 'test.db')
self.db.initialize('InitialAdmin123')
actor = self.db.authenticate('admin', 'InitialAdmin123')['id']
self.db.change_password(actor, 'InitialAdmin123', 'ChangedAdmin@2026', '')
self.client = TestClient(admin_api.create_app(self.db.path))
response = self.client.post('/api/v2/auth/login', json={'username': 'admin', 'password': 'ChangedAdmin@2026'})
self.headers = {'Authorization': 'Bearer ' + response.json()['access_token']}
def tearDown(self):
self.client.close()
self.temp.cleanup()
def test_api_authentication_and_all_scope_write_rejection(self):
self.assertEqual(self.client.get('/api/v2/knowledge/overview').status_code, 401)
self.assertEqual(self.client.get('/api/v2/knowledge/overview', headers=self.headers).status_code, 200)
result = self.client.put('/api/v2/knowledge/settings?account_id=-1', headers=self.headers, json={'enabled': True})
self.assertEqual(result.status_code, 400)
result = self.client.post('/api/v2/knowledge/search', headers=self.headers, json={'query': '预约挂号'})
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json()['hits'], [])
def test_empty_export_is_valid_and_audited(self):
response = self.client.get('/api/v2/knowledge/export', headers=self.headers)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.text, '')
with self.db.connect() as db:
self.assertIsNotNone(db.execute("SELECT 1 FROM audit_log WHERE action='knowledge.export'").fetchone())
def test_preview_auth_scope_and_enable_empty_library(self):
actor = self.db.authenticate('admin', 'ChangedAdmin@2026')['id']
ArchiveStore(self.db).import_messages(KnowledgeTest.payload(self), actor, '')
with self.db.connect() as db:
source = db.execute('SELECT id FROM archive_source_account').fetchone()[0]
body = {'source_account_id': source}
url = '/api/v2/knowledge/jobs/preview'
self.assertEqual(self.client.post(url, json=body).status_code, 401)
self.assertEqual(self.client.post(url + '?account_id=-1', json=body, headers=self.headers).status_code, 400)
response = self.client.post(url, json=body, headers=self.headers)
self.assertEqual(response.status_code, 200, response.text)
self.assertEqual(response.json()['total_messages'], 2)
enabled = self.client.put('/api/v2/knowledge/settings', json={'enabled': True}, headers=self.headers)
self.assertEqual(enabled.status_code, 400)
self.db.create_user('reviewer', 'InitialReader123', 'viewer', actor, '')
reader = self.db.authenticate('reviewer', 'InitialReader123')['id']
self.db.change_password(reader, 'InitialReader123', 'ChangedReader@2026', '')
with self.db.connect() as db:
db.execute("INSERT OR IGNORE INTO role_permissions VALUES ('viewer','knowledge:write')")
db.execute("DELETE FROM role_permissions WHERE role_code='viewer' AND permission_code='im:content:read'")
login = self.client.post('/api/v2/auth/login', json={'username': 'reviewer', 'password': 'ChangedReader@2026'})
headers = {'Authorization': 'Bearer ' + login.json()['access_token']}
self.assertEqual(self.client.post(url, json=body, headers=headers).status_code, 403)
class KnowledgeGatewayTest(TestCase):
from test_model_gateway import GatewayEndpointTest as _fixture
setUp = _fixture.setUp
_post_then_drain = _fixture._post_then_drain
_post = _fixture._post
_upstream = _fixture._upstream
def tearDown(self):
import shutil
assert self.root.parent.resolve() == Path(tempfile.gettempdir()).resolve()
shutil.rmtree(self.root)
def seed(self):
self.db = admin_backend.Database(self.db_path)
with self.db.connect() as db:
self.actor = db.execute("SELECT id FROM users WHERE username='admin'").fetchone()[0]
self.store = KnowledgeStore(self.db)
self.archive = ArchiveStore(self.db)
tenant = self.desktop_account['tenant_id']
self.archive.import_messages(KnowledgeTest.payload(self, tenant), self.actor, '')
KnowledgeTest.start_job(self, tenant)
while KnowledgeWorker(self.store).step():
pass
item = KnowledgeTest.first(self, tenant)
item['conditions'] = '通用门诊预约'
item = self.store.save(tenant, item['id'], item, self.actor, '')
self.store.transition(tenant, item['id'], item['revision'], 'approve', self.actor, '', True)
self.store.publish(tenant, item['id'], item['revision'], self.actor, '')
self.store.set_enabled(tenant, True, self.actor, '')
return item
def test_reply_receives_evidence_and_records_version(self):
import httpx
item = self.seed()
sent = []
def handler(request):
sent.append(json.loads(request.content.decode()))
return httpx.Response(200, json={'choices': [{'message': {'content': '请先确认就诊日期和科室。'}}]})
response, _ = self._post_then_drain(handler, body={'customer_text': '预约挂号需要什么材料?', 'task_id': 'rag-1'})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()['knowledge']['hits'][0]['id'], item['id'])
self.assertTrue(any('已审核知识参考' in json.dumps(body, ensure_ascii=False) for body in sent))
with self.db.connect() as db:
log = db.execute("SELECT hits_json FROM knowledge_retrieval_log WHERE task_id='rag-1'").fetchone()
self.assertEqual(json.loads(log[0])[0]['revision'], item['revision'])
def test_internal_purpose_skips_retrieval(self):
self.seed()
response, _ = self._post_then_drain(self._upstream(), body={'customer_text': '预约挂号', 'task_id': 'guard-rag', 'purpose': 'guard'})
self.assertFalse(response.json()['knowledge']['enabled'])
def test_disabled_reference_is_not_served_from_idempotency_cache(self):
item = self.seed()
body = {'customer_text': '预约挂号', 'task_id': 'cache-rag'}
headers = {'X-Idempotency-Key': 'cache-rag-key'}
response = self._post(self._upstream(), headers=headers, body=body)
self.assertTrue(response.json()['knowledge']['hits'])
self.store.transition(self.desktop_account['tenant_id'], item['id'], item['revision'], 'disable', self.actor, '')
response = self._post(self._upstream(), headers=headers, body=body)
self.assertEqual(response.json()['knowledge']['hits'], [])