from pathlib import Path p=Path(r'C:\kefu\wechat_rpa\test_knowledge.py') s=p.read_text(encoding='utf-8') s=s.replace(''' def test_sample_limit_does_not_publish_half_answer(self): self.archive.import_messages(self.payload(), self.actor, '')''',''' def test_sample_limit_does_not_publish_half_answer(self): payload = self.payload(messages=[('inbound', '预约挂号需要什么材料?'), ('outbound', '请先确认就诊日期。'), ('outbound', '还需要确认就诊科室。')]) self.archive.import_messages(payload, self.actor, '')''') s=s.replace(" item['valid_until'] = '2000-01-01'\n self.publish(item)\n self.assertFalse(KnowledgeRetriever(self.store).search('default', '预约挂号')['hits'])", """ 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'])""") pos=s.index('\n\nclass KnowledgeApiTest') s=s[:pos]+''' 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)) ''' + s[pos:] pos=s.index('\n\nclass KnowledgeGatewayTest') s=s[:pos]+''' 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) ''' + s[pos:] p.write_text(s,encoding='utf-8',newline='\n') print('Added workflow regression tests.')