Mercurial
comparison mrjunejune/inference/mock_sidecar_test.py @ 261:b401627fc49e
Add JRPG mock flows and interactive previews
Add scripted mock SSE commands, custom event forwarding, animated chat turns, full-height message navigation, and a cyberpunk resume dossier.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <mrjunejune@users.noreply.github.com> |
|---|---|
| date | Wed, 05 Aug 2026 20:38:32 -0700 |
| parents | |
| children | 056790c4fb0d |
comparison
equal
deleted
inserted
replaced
| 260:1f9877b637e9 | 261:b401627fc49e |
|---|---|
| 1 import asyncio | |
| 2 import json | |
| 3 import pathlib | |
| 4 import tempfile | |
| 5 import unittest | |
| 6 | |
| 7 from mrjunejune.inference.mock_sidecar import ( | |
| 8 MockConfig, | |
| 9 MockSidecar, | |
| 10 load_mock_config, | |
| 11 ) | |
| 12 | |
| 13 | |
| 14 class MockSidecarTest(unittest.IsolatedAsyncioTestCase): | |
| 15 async def asyncSetUp(self): | |
| 16 self.events = [] | |
| 17 | |
| 18 async def emit(payload): | |
| 19 self.events.append(payload) | |
| 20 | |
| 21 packaged = load_mock_config( | |
| 22 pathlib.Path(__file__).with_name("mock_responses.json") | |
| 23 ) | |
| 24 self.config = MockConfig( | |
| 25 delay_ms=0, | |
| 26 fallback=packaged.fallback, | |
| 27 commands=packaged.commands, | |
| 28 ) | |
| 29 self.sidecar = MockSidecar(emit, self.config) | |
| 30 | |
| 31 def test_packaged_commands_are_selectable(self): | |
| 32 self.assertEqual(self.config.select("!hello").name, "!hello") | |
| 33 self.assertEqual(self.config.select("!response please").name, "!response") | |
| 34 self.assertEqual(self.config.select("ordinary prompt").name, "!response") | |
| 35 self.assertEqual(self.config.select("!tool").name, "!tool") | |
| 36 self.assertEqual(self.config.select("!plan").name, "!plan") | |
| 37 | |
| 38 def test_response_file_rejects_invalid_scripts(self): | |
| 39 invalid_configs = ( | |
| 40 { | |
| 41 "fallback": "!hello", | |
| 42 "commands": { | |
| 43 "!hello": { | |
| 44 "events": [ | |
| 45 { | |
| 46 "type": "assistant.completed", | |
| 47 "content": "Hello", | |
| 48 } | |
| 49 ], | |
| 50 "unknown": True, | |
| 51 } | |
| 52 }, | |
| 53 }, | |
| 54 { | |
| 55 "fallback": "hello", | |
| 56 "commands": { | |
| 57 "hello": { | |
| 58 "events": [ | |
| 59 { | |
| 60 "type": "assistant.completed", | |
| 61 "content": "Hello", | |
| 62 } | |
| 63 ] | |
| 64 } | |
| 65 }, | |
| 66 }, | |
| 67 { | |
| 68 "fallback": "!hello", | |
| 69 "commands": { | |
| 70 "!hello": { | |
| 71 "events": [ | |
| 72 { | |
| 73 "type": "assistant.delta", | |
| 74 "delta": "Different", | |
| 75 }, | |
| 76 { | |
| 77 "type": "assistant.completed", | |
| 78 "content": "Hello", | |
| 79 }, | |
| 80 ] | |
| 81 } | |
| 82 }, | |
| 83 }, | |
| 84 { | |
| 85 "delay_ms": 60001, | |
| 86 "fallback": "!hello", | |
| 87 "commands": { | |
| 88 "!hello": { | |
| 89 "events": [ | |
| 90 { | |
| 91 "type": "assistant.completed", | |
| 92 "content": "Hello", | |
| 93 } | |
| 94 ] | |
| 95 } | |
| 96 }, | |
| 97 }, | |
| 98 { | |
| 99 "fallback": "!hello", | |
| 100 "commands": { | |
| 101 "!hello": { | |
| 102 "events": [ | |
| 103 { | |
| 104 "type": "assistant.completed", | |
| 105 "content": "Hello", | |
| 106 }, | |
| 107 { | |
| 108 "type": "assistant.delta", | |
| 109 "delta": "Hello", | |
| 110 }, | |
| 111 ] | |
| 112 } | |
| 113 }, | |
| 114 }, | |
| 115 ) | |
| 116 for payload in invalid_configs: | |
| 117 with self.subTest(payload=payload): | |
| 118 with tempfile.TemporaryDirectory() as root: | |
| 119 path = pathlib.Path(root) / "responses.json" | |
| 120 path.write_text(json.dumps(payload), encoding="utf-8") | |
| 121 with self.assertRaises(ValueError): | |
| 122 load_mock_config(path) | |
| 123 | |
| 124 async def test_hello_command_streams_scripted_events(self): | |
| 125 await self.sidecar.dispatch( | |
| 126 { | |
| 127 "command": "turn.start", | |
| 128 "request_id": "request-1", | |
| 129 "conversation_id": "conversation-1", | |
| 130 "prompt": "!hello", | |
| 131 } | |
| 132 ) | |
| 133 await self.sidecar.wait_for_idle() | |
| 134 | |
| 135 self.assertEqual( | |
| 136 [event["type"] for event in self.events], | |
| 137 [ | |
| 138 "turn.accepted", | |
| 139 "assistant.delta", | |
| 140 "assistant.delta", | |
| 141 "assistant.completed", | |
| 142 "turn.done", | |
| 143 ], | |
| 144 ) | |
| 145 deltas = "".join( | |
| 146 event["delta"] | |
| 147 for event in self.events | |
| 148 if event["type"] == "assistant.delta" | |
| 149 ) | |
| 150 completed = next( | |
| 151 event["content"] | |
| 152 for event in self.events | |
| 153 if event["type"] == "assistant.completed" | |
| 154 ) | |
| 155 self.assertEqual(deltas, completed) | |
| 156 self.assertEqual(self.events[-1]["mock_command"], "!hello") | |
| 157 | |
| 158 async def test_tool_command_preserves_custom_event_payloads(self): | |
| 159 await self.sidecar.dispatch( | |
| 160 { | |
| 161 "command": "turn.start", | |
| 162 "request_id": "request-2", | |
| 163 "conversation_id": "conversation-2", | |
| 164 "prompt": "!tool", | |
| 165 } | |
| 166 ) | |
| 167 await self.sidecar.wait_for_idle() | |
| 168 | |
| 169 started = next( | |
| 170 event for event in self.events if event["type"] == "tool.started" | |
| 171 ) | |
| 172 completed = next( | |
| 173 event for event in self.events if event["type"] == "tool.completed" | |
| 174 ) | |
| 175 self.assertEqual(started["tool"]["name"], "site_search") | |
| 176 self.assertEqual(completed["tool"]["result"]["matches"], 3) | |
| 177 | |
| 178 async def test_error_command_emits_failed_turn(self): | |
| 179 await self.sidecar.dispatch( | |
| 180 { | |
| 181 "command": "turn.start", | |
| 182 "request_id": "request-3", | |
| 183 "conversation_id": "conversation-3", | |
| 184 "prompt": "!error", | |
| 185 } | |
| 186 ) | |
| 187 await self.sidecar.wait_for_idle() | |
| 188 | |
| 189 self.assertEqual( | |
| 190 [event["type"] for event in self.events], | |
| 191 ["turn.accepted", "turn.error", "turn.done"], | |
| 192 ) | |
| 193 self.assertTrue(self.events[-1]["failed"]) | |
| 194 | |
| 195 async def test_abort_stops_active_command(self): | |
| 196 self.sidecar._config = MockConfig( | |
| 197 delay_ms=50, | |
| 198 fallback=self.config.fallback, | |
| 199 commands=self.config.commands, | |
| 200 ) | |
| 201 await self.sidecar.dispatch( | |
| 202 { | |
| 203 "command": "turn.start", | |
| 204 "request_id": "request-4", | |
| 205 "conversation_id": "conversation-4", | |
| 206 "prompt": "!response", | |
| 207 } | |
| 208 ) | |
| 209 await asyncio.sleep(0.01) | |
| 210 await self.sidecar.dispatch( | |
| 211 { | |
| 212 "command": "turn.abort", | |
| 213 "request_id": "abort-4", | |
| 214 "conversation_id": "conversation-4", | |
| 215 } | |
| 216 ) | |
| 217 | |
| 218 target_done = next( | |
| 219 event | |
| 220 for event in self.events | |
| 221 if event["type"] == "turn.done" | |
| 222 and event["request_id"] == "request-4" | |
| 223 ) | |
| 224 self.assertTrue(target_done["aborted"]) | |
| 225 | |
| 226 | |
| 227 if __name__ == "__main__": | |
| 228 unittest.main() |