#!/usr/bin/env python3 """Test S4: SSE streaming endpoint and call_hermes_stream().""" import sys, os, unittest from unittest.mock import patch, MagicMock from io import StringIO # Add wechat-viewer to path sys.path.insert(0, os.path.expanduser("~/wechat-viewer")) class TestHermesStream(unittest.TestCase): """Test the call_hermes_stream() generator.""" def test_call_hermes_stream_exists(self): """call_hermes_stream() should exist as a callable function in app module.""" import app self.assertTrue(hasattr(app, 'call_hermes_stream'), "app must have call_hermes_stream function") self.assertTrue(callable(app.call_hermes_stream), "call_hermes_stream must be callable") @patch('app.subprocess.Popen') def test_stream_yields_chunks(self, mock_popen): """call_hermes_stream should yield text chunks from subprocess stdout.""" lines = ["Hello", " ", "world\n", "How are you?\n"] mock_proc = MagicMock() mock_proc.stdout.__iter__.return_value = iter(lines) mock_proc.poll.return_value = None mock_proc.returncode = 0 mock_popen.return_value = mock_proc import app chunks = list(app.call_hermes_stream("test input")) self.assertGreater(len(chunks), 0, "Should yield at least one chunk") self.assertIn("Hello", chunks[0]) mock_popen.assert_called_once() @patch('app.subprocess.Popen') def test_stream_handles_empty_output(self, mock_popen): """call_hermes_stream should handle empty stdout gracefully.""" mock_proc = MagicMock() mock_proc.stdout.readline.return_value = "" mock_proc.poll.return_value = None mock_popen.return_value = mock_proc import app chunks = list(app.call_hermes_stream("test")) self.assertEqual(len(chunks), 0, "Empty output should yield no chunks") def test_stream_route_exists(self): """GET /api/conversations//stream should exist in app.py.""" app_path = os.path.expanduser("~/wechat-viewer/app.py") code = open(app_path).read() self.assertIn("/stream", code, "Stream route should exist in app.py") self.assertIn("text/event-stream", code, "Should use text/event-stream content type") def test_response_class_import(self): """Flask Response class should be importable for SSE.""" from flask import Response self.assertTrue(callable(Response), "Flask Response must be callable") class TestSSEFormat(unittest.TestCase): """Test SSE wire format.""" def test_sse_format(self): """SSE format should be 'data: \\n\\n'.""" chunks = ["Hello", " ", "world"] sse_events = [] for chunk in chunks: sse_events.append(f"data: {chunk}\n\n") sse_events.append("data: [DONE]\n\n") output = "".join(sse_events) self.assertIn("data: Hello", output) self.assertIn("data: [DONE]", output) self.assertTrue(output.endswith("\n\n")) if __name__ == '__main__': # Quick check if the function/route exists app_path = os.path.expanduser("~/wechat-viewer/app.py") code = open(app_path).read() has_stream_func = 'def call_hermes_stream' in code has_stream_route = '/stream' in code and 'text/event-stream' in code print(f"call_hermes_stream defined: {has_stream_func}") print(f"SSE route + text/event-stream: {has_stream_route}") if not has_stream_func or not has_stream_route: print("RED: Missing SSE implementation — test expected to fail") sys.exit(1) unittest.main(verbosity=2)