117 lines
5.2 KiB
Python
117 lines
5.2 KiB
Python
"""Exercise transcription -> question/risk/model/send using only synthetic transports."""
|
|
import time
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from protocol_engine import session_key, model_context_text
|
|
import test_native_protocol_pipeline as helpers
|
|
from test_native_protocol_pipeline import message, ACCOUNT, CONV
|
|
|
|
|
|
class ProtocolVoiceTests(unittest.TestCase):
|
|
setUp = helpers.EngineTests.setUp
|
|
make_bot = helpers.EngineTests.make_bot
|
|
enqueue = helpers.EngineTests.enqueue
|
|
|
|
def voice(self, n=2, status='pending', text='语音内容合成测试', **kwargs):
|
|
m = message(n, **kwargs)
|
|
m.update(content_type=16, voice_status=status, content='[语音待转文字]')
|
|
if status == 'ready':
|
|
m.update(voice_transcribed=True, content='[语音转文字] ' + text,
|
|
voice_binding=f"{m['account']}\0{m['conv_id']}\0{m['server_id']}")
|
|
return m
|
|
|
|
def test_transcribed_voice_reaches_model_and_selective_review_as_text(self):
|
|
from protocol_engine import ProtocolBot
|
|
self.bot._needs_review = lambda state: ProtocolBot._needs_review(self.bot, state)
|
|
self.bot._assistant_pending = lambda state: False
|
|
v = self.voice(status='ready')
|
|
with mock.patch('review_policy.selective_review_reason', return_value='合成审核规则') as policy:
|
|
self.enqueue(v)
|
|
self.gen.assert_called_once()
|
|
context = self.gen.call_args.args[0]
|
|
self.assertIn('语音内容合成测试', model_context_text(context))
|
|
state = self.bot._pending[session_key(ACCOUNT, CONV)]
|
|
self.assertIn('语音内容合成测试', policy.call_args.args[1])
|
|
self.assertTrue(state['awaiting_review'])
|
|
self.assertFalse(state.get('manual_required'))
|
|
self.assertFalse(self.sender.calls)
|
|
|
|
def test_pending_voice_delays_only_its_conversation_then_resumes_on_cache_update(self):
|
|
v = self.voice()
|
|
self.enqueue(v)
|
|
key = session_key(ACCOUNT, CONV)
|
|
self.assertEqual(self.bot._pending[key]['stage'], 'voice_transcribing')
|
|
self.gen.assert_not_called()
|
|
self.db.add(message(4, conv='S:100_201'))
|
|
self.bot.poll_once()
|
|
self.assertEqual([c[2] for c in self.sender.calls], ['S:100_201'])
|
|
v.update(self.voice(status='ready')) # Same server ID/row; no new-message event.
|
|
self.bot._pending[key]['ready_at'] = 0
|
|
self.bot.poll_once()
|
|
self.assertEqual([c[2] for c in self.sender.calls], ['S:100_201', CONV])
|
|
self.assertFalse(self.bot.stop_event.is_set())
|
|
|
|
def test_mixed_voice_and_text_waits_and_preserves_both(self):
|
|
v = self.voice()
|
|
self.db.add(v)
|
|
self.enqueue(message(3, text='补充文字'))
|
|
key = session_key(ACCOUNT, CONV)
|
|
self.assertEqual(self.bot._pending[key]['stage'], 'voice_transcribing')
|
|
v.update(self.voice(status='ready'))
|
|
self.bot._pending[key]['ready_at'] = 0
|
|
self.bot.poll_once()
|
|
context = self.gen.call_args.args[0]
|
|
self.assertIn('补充文字', model_context_text(context))
|
|
self.assertIn('语音内容合成测试', model_context_text(context))
|
|
self.assertEqual(len(self.sender.calls), 1)
|
|
|
|
def test_failed_transcription_enters_manual_and_never_calls_model(self):
|
|
v = self.voice(status='error')
|
|
v['voice_error_code'] = 'empty_audio'
|
|
self.enqueue(v)
|
|
state = self.bot._pending[session_key(ACCOUNT, CONV)]
|
|
self.assertTrue(state['manual_required'])
|
|
self.assertIn('empty_audio', state['manual_reason'])
|
|
self.gen.assert_not_called()
|
|
self.assertFalse(self.sender.calls)
|
|
|
|
def test_missing_voice_times_out_without_global_stop_or_guess(self):
|
|
self.enqueue(self.voice(status='missing'))
|
|
state = self.bot._pending[session_key(ACCOUNT, CONV)]
|
|
state.update(voice_wait_started_at=time.time()-181, ready_at=0)
|
|
self.bot.poll_once()
|
|
self.assertTrue(state['manual_required'])
|
|
self.assertIn('超时', state['manual_reason'])
|
|
self.assertFalse(self.bot.stop_event.is_set())
|
|
self.gen.assert_not_called()
|
|
|
|
def test_human_reply_while_voice_pending_discards_old_question(self):
|
|
self.enqueue(self.voice())
|
|
self.db.add(message(3, self=True, text='人工答复'))
|
|
self.bot._pending[session_key(ACCOUNT, CONV)]['ready_at'] = 0
|
|
self.bot.poll_once()
|
|
self.assertFalse(self.bot._pending)
|
|
self.gen.assert_not_called()
|
|
self.assertFalse(self.sender.calls)
|
|
|
|
def test_new_media_after_voice_still_needs_manual(self):
|
|
self.db.add(self.voice(status='ready'))
|
|
image = message(3)
|
|
image['content_type'] = 3
|
|
self.enqueue(image)
|
|
self.assertTrue(self.bot._pending[session_key(ACCOUNT, CONV)]['manual_required'])
|
|
self.gen.assert_not_called()
|
|
|
|
def test_spoofed_or_cross_conversation_transcription_does_not_reach_model(self):
|
|
v = self.voice(status='ready')
|
|
v['voice_binding'] = '101\0M:200\0'+'2'
|
|
self.enqueue(v)
|
|
self.gen.assert_not_called()
|
|
self.assertFalse(self.sender.calls)
|
|
self.assertEqual(self.bot._pending[session_key(ACCOUNT, CONV)]['stage'], 'voice_transcribing')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|