test_deepseek_proxy_stream.py 4.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import importlib.util
  2. import json
  3. import os
  4. from pathlib import Path
  5. import time
  6. import unittest
  7. from unittest.mock import patch
  8. from scripts.tests import test_deepseek_proxy as ordinary
  9. spec = importlib.util.spec_from_file_location('stream_probe', Path(__file__).resolve().parents[1] / 'test_deepseek_proxy_stream.py')
  10. probe = importlib.util.module_from_spec(spec)
  11. spec.loader.exec_module(probe)
  12. def frame(text=None, finish=None):
  13. return ('data: ' + json.dumps({'choices': [{'index': 0, 'delta': {'content': text},
  14. 'finish_reason': finish}]}, ensure_ascii=False) + '\r\n\r\n').encode()
  15. class StreamProbeTests(unittest.TestCase):
  16. setUp = ordinary.DeepSeekProxyTests.setUp
  17. tearDown = ordinary.DeepSeekProxyTests.tearDown
  18. def run_probe(self):
  19. with patch.dict(os.environ, self.environment), patch.object(probe, 'load_env_values', return_value={}):
  20. return probe.probe(timeout=2)
  21. def test_real_chunked_sse_keepalive(self):
  22. self.headers = {'Content-Type': 'text/event-stream; charset=utf-8', 'Transfer-Encoding': 'chunked'}
  23. frames = [b': heartbeat\r\n\r\n', frame('你'), frame('好'), frame(finish='stop'),
  24. b'data: {"choices":[],"usage":{}}\r\n\r\n', b'data: [DONE]\r\n\r\n']
  25. self.body = b''.join(f'{len(x):x}\r\n'.encode() + x + b'\r\n' for x in frames) + b'0\r\n\r\n'
  26. result = self.run_probe()
  27. self.assertTrue(result['ok'], result)
  28. self.assertTrue(result['done_received'])
  29. self.assertTrue(result['response_complete'])
  30. self.assertEqual(result['assistant_text'], '你好')
  31. self.assertEqual(result['content_chunks'], 2)
  32. self.assertEqual(self.requests[0]['connection'], 'keep-alive')
  33. self.assertTrue(self.requests[0]['payload']['stream'])
  34. self.assertEqual(self.requests[0]['path'], '/proxy/?servertype=deepseek&proxyToken=fake%2Btoken%26value')
  35. def test_failure_matrix(self):
  36. self.headers = {'Content-Type': 'text/event-stream'}
  37. for body, code in (
  38. (frame('x', 'stop'), 'stream_incomplete'),
  39. (frame('x') + b'data: [DONE]\n\n', 'stream_incomplete'),
  40. (frame(None, 'stop') + b'data: [DONE]\n\n', 'empty_stream_content'),
  41. (frame('x', 'length') + b'data: [DONE]\n\n', 'incomplete_completion'),
  42. (b'data: {broken}\n\n', 'invalid_sse'),
  43. (b'data: {"error":"fake-api-key"}\n\n', 'provider_error'),
  44. ):
  45. with self.subTest(code=code):
  46. self.body = body
  47. result = self.run_probe()
  48. self.assertFalse(result['ok'])
  49. self.assertEqual(result['code'], code)
  50. self.assertNotIn('fake-api-key', json.dumps(result))
  51. def test_done_cannot_hide_broken_http_chunking(self):
  52. self.headers = {'Content-Type': 'text/event-stream', 'Transfer-Encoding': 'chunked'}
  53. body = frame('x', 'stop') + b'data: [DONE]\n\n'
  54. self.body = f'{len(body):x}\r\n'.encode() + body + b'\r\n'
  55. result = self.run_probe()
  56. self.assertFalse(result['ok'])
  57. self.assertEqual(result['code'], 'response_incomplete')
  58. def test_json_fallback_and_http_error_are_not_stream_success(self):
  59. self.assertEqual(self.run_probe()['code'], 'unexpected_response')
  60. self.status = 500
  61. self.assertEqual(self.run_probe()['code'], 'http_error')
  62. def test_bytewise_utf8_multiline_and_redaction(self):
  63. wire = frame('你好 fake-api-key', 'stop') + b'data: [DONE]\n\n'
  64. class Response:
  65. def __init__(self):
  66. self.parts = iter(bytes([b]) for b in wire)
  67. def read1(self, _size):
  68. return next(self.parts, b'')
  69. result = {'ok': False, 'content_length': len(wire), 'transfer_encoding': ''}
  70. probe.read_sse(Response(), result, ['fake-api-key'], time.perf_counter(), 2)
  71. self.assertTrue(result['ok'])
  72. self.assertNotIn('fake-api-key', result['assistant_text'])
  73. def test_cli_and_timeout_cleanup(self):
  74. import contextlib
  75. import io
  76. with patch.object(probe.sys, 'argv', ['test_deepseek_proxy_stream.py']), patch.object(probe, 'probe', return_value={'ok': True}), contextlib.redirect_stdout(io.StringIO()):
  77. self.assertEqual(probe.main(), 0)
  78. with patch.object(probe, 'HTTPConnection') as connection:
  79. connection.return_value.getresponse.side_effect = TimeoutError
  80. self.assertEqual(self.run_probe()['code'], 'timeout')
  81. connection.return_value.close.assert_called_once()