repl-toolkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of repl-toolkit might be problematic. Click here for more details.

@@ -0,0 +1,682 @@
1
+ """
2
+ Tests for headless mode functionality with action framework support.
3
+ """
4
+
5
+ import pytest
6
+ import asyncio
7
+ import sys
8
+ from unittest.mock import Mock, AsyncMock, patch
9
+ from io import StringIO
10
+
11
+ from repl_toolkit.headless_repl import HeadlessREPL, run_headless_mode
12
+ from repl_toolkit.actions import ActionRegistry, Action, ActionContext
13
+
14
+
15
+ class MockAsyncBackend:
16
+ """Mock backend for headless testing."""
17
+
18
+ def __init__(self, should_succeed=True):
19
+ self.should_succeed = should_succeed
20
+ self.inputs_received = []
21
+ self.call_count = 0
22
+
23
+ async def handle_input(self, user_input: str) -> bool:
24
+ self.inputs_received.append(user_input)
25
+ self.call_count += 1
26
+ # Simulate some processing time
27
+ await asyncio.sleep(0.01)
28
+ return self.should_succeed
29
+
30
+
31
+ class TestHeadlessREPL:
32
+ """Test HeadlessREPL class functionality."""
33
+
34
+ def setup_method(self):
35
+ """Set up test fixtures."""
36
+ self.backend = MockAsyncBackend()
37
+ self.action_registry = ActionRegistry()
38
+
39
+ def test_headless_repl_initialization(self):
40
+ """Test HeadlessREPL initialization."""
41
+ repl = HeadlessREPL()
42
+
43
+ assert repl.buffer == ""
44
+ assert repl.send_count == 0
45
+ assert repl.total_success is True
46
+ assert repl.running is True
47
+ assert isinstance(repl.action_registry, ActionRegistry)
48
+
49
+ def test_headless_repl_with_custom_registry(self):
50
+ """Test HeadlessREPL with custom action registry."""
51
+ custom_registry = ActionRegistry()
52
+ repl = HeadlessREPL(action_registry=custom_registry)
53
+
54
+ assert repl.action_registry is custom_registry
55
+
56
+ def test_add_to_buffer(self):
57
+ """Test buffer management."""
58
+ repl = HeadlessREPL()
59
+
60
+ # Add first line
61
+ repl._add_to_buffer("Line 1")
62
+ assert repl.buffer == "Line 1"
63
+
64
+ # Add second line
65
+ repl._add_to_buffer("Line 2")
66
+ assert repl.buffer == "Line 1\nLine 2"
67
+
68
+ # Add empty line
69
+ repl._add_to_buffer("")
70
+ assert repl.buffer == "Line 1\nLine 2\n"
71
+
72
+ @pytest.mark.asyncio
73
+ async def test_execute_send_with_content(self):
74
+ """Test /send execution with buffer content."""
75
+ repl = HeadlessREPL()
76
+ repl._add_to_buffer("Test content")
77
+
78
+ await repl._execute_send(self.backend, "test")
79
+
80
+ assert repl.send_count == 1
81
+ assert repl.buffer == "" # Buffer cleared after send
82
+ assert self.backend.inputs_received == ["Test content"]
83
+ assert repl.total_success is True
84
+
85
+ @pytest.mark.asyncio
86
+ async def test_execute_send_empty_buffer(self):
87
+ """Test /send execution with empty buffer."""
88
+ repl = HeadlessREPL()
89
+
90
+ await repl._execute_send(self.backend, "test")
91
+
92
+ assert repl.send_count == 0 # No send occurred
93
+ assert repl.buffer == ""
94
+ assert self.backend.inputs_received == []
95
+
96
+ @pytest.mark.asyncio
97
+ async def test_execute_send_backend_failure(self):
98
+ """Test /send execution with backend failure."""
99
+ self.backend.should_succeed = False
100
+ repl = HeadlessREPL()
101
+ repl._add_to_buffer("Test content")
102
+
103
+ await repl._execute_send(self.backend, "test")
104
+
105
+ assert repl.send_count == 1
106
+ assert repl.buffer == "" # Buffer still cleared
107
+ assert repl.total_success is False # Marked as failed
108
+ assert self.backend.inputs_received == ["Test content"]
109
+
110
+ @pytest.mark.asyncio
111
+ async def test_execute_send_backend_exception(self):
112
+ """Test /send execution with backend exception."""
113
+ backend = AsyncMock()
114
+ backend.handle_input.side_effect = Exception("Backend error")
115
+
116
+ repl = HeadlessREPL()
117
+ repl._add_to_buffer("Test content")
118
+
119
+ await repl._execute_send(backend, "test")
120
+
121
+ assert repl.send_count == 1
122
+ assert repl.buffer == "" # Buffer cleared even on exception
123
+ assert repl.total_success is False
124
+
125
+ @pytest.mark.asyncio
126
+ async def test_handle_eof_with_content(self):
127
+ """Test EOF handling with buffer content."""
128
+ repl = HeadlessREPL()
129
+ repl._add_to_buffer("Final content")
130
+
131
+ await repl._handle_eof(self.backend)
132
+
133
+ assert repl.send_count == 1
134
+ assert repl.buffer == ""
135
+ assert self.backend.inputs_received == ["Final content"]
136
+
137
+ @pytest.mark.asyncio
138
+ async def test_handle_eof_empty_buffer(self):
139
+ """Test EOF handling with empty buffer."""
140
+ repl = HeadlessREPL()
141
+
142
+ await repl._handle_eof(self.backend)
143
+
144
+ assert repl.send_count == 0
145
+ assert self.backend.inputs_received == []
146
+
147
+ def test_execute_command(self):
148
+ """Test command execution through action system."""
149
+ executed_commands = []
150
+
151
+ def mock_handle_command(command, **kwargs):
152
+ executed_commands.append((command, kwargs))
153
+
154
+ repl = HeadlessREPL()
155
+ repl.action_registry.handle_command = mock_handle_command
156
+
157
+ repl._execute_command("/test arg1 arg2")
158
+
159
+ assert len(executed_commands) == 1
160
+ command, kwargs = executed_commands[0]
161
+ assert command == "/test arg1 arg2"
162
+ assert kwargs['headless_mode'] is True
163
+ assert kwargs['buffer'] == ""
164
+
165
+ def test_execute_command_with_buffer(self):
166
+ """Test command execution with buffer content."""
167
+ executed_commands = []
168
+
169
+ def mock_handle_command(command, **kwargs):
170
+ executed_commands.append((command, kwargs))
171
+
172
+ repl = HeadlessREPL()
173
+ repl.action_registry.handle_command = mock_handle_command
174
+ repl._add_to_buffer("Buffer content")
175
+
176
+ repl._execute_command("/status")
177
+
178
+ assert len(executed_commands) == 1
179
+ command, kwargs = executed_commands[0]
180
+ assert command == "/status"
181
+ assert kwargs['buffer'] == "Buffer content"
182
+
183
+ def test_execute_command_exception(self):
184
+ """Test command execution with exception."""
185
+ def mock_handle_command(command, **kwargs):
186
+ raise Exception("Command error")
187
+
188
+ repl = HeadlessREPL()
189
+ repl.action_registry.handle_command = mock_handle_command
190
+
191
+ # Should not raise exception
192
+ repl._execute_command("/error")
193
+
194
+ # REPL should continue functioning
195
+ assert repl.buffer == ""
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_run_with_initial_message(self):
199
+ """Test run with initial message."""
200
+ repl = HeadlessREPL()
201
+
202
+ with patch.object(repl, '_stdin_loop') as mock_stdin_loop:
203
+ result = await repl.run(self.backend, "Initial message")
204
+
205
+ assert result is True
206
+ assert repl.action_registry.backend is self.backend
207
+ assert self.backend.inputs_received == ["Initial message"]
208
+ mock_stdin_loop.assert_called_once_with(self.backend)
209
+
210
+ @pytest.mark.asyncio
211
+ async def test_run_without_initial_message(self):
212
+ """Test run without initial message."""
213
+ repl = HeadlessREPL()
214
+
215
+ with patch.object(repl, '_stdin_loop') as mock_stdin_loop:
216
+ result = await repl.run(self.backend)
217
+
218
+ assert result is True
219
+ assert self.backend.inputs_received == []
220
+ mock_stdin_loop.assert_called_once_with(self.backend)
221
+
222
+ @pytest.mark.asyncio
223
+ async def test_run_initial_message_failure(self):
224
+ """Test run with initial message backend failure."""
225
+ self.backend.should_succeed = False
226
+ repl = HeadlessREPL()
227
+
228
+ with patch.object(repl, '_stdin_loop') as mock_stdin_loop:
229
+ result = await repl.run(self.backend, "Initial message")
230
+
231
+ assert result is False # Overall failure due to initial message
232
+ assert self.backend.inputs_received == ["Initial message"]
233
+ mock_stdin_loop.assert_called_once_with(self.backend)
234
+
235
+ @pytest.mark.asyncio
236
+ async def test_run_exception_handling(self):
237
+ """Test run with exception in stdin loop."""
238
+ repl = HeadlessREPL()
239
+
240
+ def mock_stdin_loop(backend):
241
+ raise Exception("stdin error")
242
+
243
+ with patch.object(repl, '_stdin_loop', side_effect=mock_stdin_loop):
244
+ result = await repl.run(self.backend)
245
+
246
+ assert result is False
247
+
248
+
249
+ class TestStdinProcessing:
250
+ """Test stdin processing functionality."""
251
+
252
+ def setup_method(self):
253
+ """Set up test fixtures."""
254
+ self.backend = MockAsyncBackend()
255
+
256
+ @pytest.mark.asyncio
257
+ async def test_stdin_loop_simple_send(self):
258
+ """Test stdin loop with simple content and send."""
259
+ stdin_input = "Line 1\nLine 2\n/send\n"
260
+
261
+ repl = HeadlessREPL()
262
+
263
+ with patch('sys.stdin', StringIO(stdin_input)):
264
+ await repl._stdin_loop(self.backend)
265
+
266
+ assert repl.send_count == 1
267
+ assert repl.buffer == "" # Cleared after send
268
+ assert self.backend.inputs_received == ["Line 1\nLine 2"]
269
+
270
+ @pytest.mark.asyncio
271
+ async def test_stdin_loop_multiple_sends(self):
272
+ """Test stdin loop with multiple send cycles."""
273
+ stdin_input = "Line 1\nLine 2\n/send\nLine 3\nLine 4\n/send\n"
274
+
275
+ repl = HeadlessREPL()
276
+
277
+ with patch('sys.stdin', StringIO(stdin_input)):
278
+ await repl._stdin_loop(self.backend)
279
+
280
+ assert repl.send_count == 2
281
+ assert repl.buffer == ""
282
+ assert self.backend.inputs_received == ["Line 1\nLine 2", "Line 3\nLine 4"]
283
+
284
+ @pytest.mark.asyncio
285
+ async def test_stdin_loop_eof_with_content(self):
286
+ """Test stdin loop with EOF and remaining content."""
287
+ stdin_input = "Line 1\nLine 2\n" # No /send, just EOF
288
+
289
+ repl = HeadlessREPL()
290
+
291
+ with patch('sys.stdin', StringIO(stdin_input)):
292
+ await repl._stdin_loop(self.backend)
293
+
294
+ assert repl.send_count == 1 # EOF triggered send
295
+ assert repl.buffer == ""
296
+ assert self.backend.inputs_received == ["Line 1\nLine 2"]
297
+
298
+ @pytest.mark.asyncio
299
+ async def test_stdin_loop_commands_between_content(self):
300
+ """Test stdin loop with commands between content."""
301
+ stdin_input = "Line 1\n/help\nLine 2\n/send\n"
302
+
303
+ executed_commands = []
304
+
305
+ def mock_handle_command(command, **kwargs):
306
+ executed_commands.append(command)
307
+
308
+ repl = HeadlessREPL()
309
+ repl.action_registry.handle_command = mock_handle_command
310
+
311
+ with patch('sys.stdin', StringIO(stdin_input)):
312
+ await repl._stdin_loop(self.backend)
313
+
314
+ assert repl.send_count == 1
315
+ assert self.backend.inputs_received == ["Line 1\nLine 2"]
316
+ assert executed_commands == ["/help"]
317
+
318
+ @pytest.mark.asyncio
319
+ async def test_stdin_loop_empty_lines(self):
320
+ """Test stdin loop with empty lines."""
321
+ stdin_input = "Line 1\n\nLine 2\n/send\n"
322
+
323
+ repl = HeadlessREPL()
324
+
325
+ with patch('sys.stdin', StringIO(stdin_input)):
326
+ await repl._stdin_loop(self.backend)
327
+
328
+ assert self.backend.inputs_received == ["Line 1\n\nLine 2"]
329
+
330
+ @pytest.mark.asyncio
331
+ async def test_stdin_loop_only_commands(self):
332
+ """Test stdin loop with only commands, no content."""
333
+ stdin_input = "/help\n/status\n/send\n"
334
+
335
+ executed_commands = []
336
+
337
+ def mock_handle_command(command, **kwargs):
338
+ executed_commands.append(command)
339
+
340
+ repl = HeadlessREPL()
341
+ repl.action_registry.handle_command = mock_handle_command
342
+
343
+ with patch('sys.stdin', StringIO(stdin_input)):
344
+ await repl._stdin_loop(self.backend)
345
+
346
+ assert repl.send_count == 0 # No send because buffer was empty
347
+ assert self.backend.inputs_received == []
348
+ assert executed_commands == ["/help", "/status"]
349
+
350
+ @pytest.mark.asyncio
351
+ async def test_stdin_loop_keyboard_interrupt(self):
352
+ """Test stdin loop with KeyboardInterrupt - should NOT send buffer."""
353
+ repl = HeadlessREPL()
354
+ repl._add_to_buffer("Interrupted content")
355
+
356
+ def mock_readline():
357
+ raise KeyboardInterrupt()
358
+
359
+ with patch('sys.stdin.readline', side_effect=mock_readline):
360
+ # KeyboardInterrupt should be caught and handled gracefully
361
+ try:
362
+ await repl._stdin_loop(self.backend)
363
+ except KeyboardInterrupt:
364
+ pass # Expected behavior - KeyboardInterrupt propagates
365
+
366
+ # KeyboardInterrupt should NOT trigger EOF handling or send buffer
367
+ assert repl.send_count == 0 # No sends should occur
368
+ assert self.backend.inputs_received == [] # No content should be sent
369
+ assert repl.buffer == "Interrupted content" # Buffer should remain intact
370
+
371
+
372
+ class TestRunHeadlessMode:
373
+ """Test run_headless_mode convenience function."""
374
+
375
+ def setup_method(self):
376
+ """Set up test fixtures."""
377
+ self.backend = MockAsyncBackend()
378
+
379
+ @pytest.mark.asyncio
380
+ async def test_run_headless_mode_basic(self):
381
+ """Test basic run_headless_mode functionality."""
382
+ with patch('repl_toolkit.headless_repl.HeadlessREPL') as mock_repl_class:
383
+ mock_repl = Mock()
384
+ mock_repl.run = AsyncMock(return_value=True)
385
+ mock_repl_class.return_value = mock_repl
386
+
387
+ result = await run_headless_mode(
388
+ backend=self.backend,
389
+ initial_message="test message"
390
+ )
391
+
392
+ assert result is True
393
+ mock_repl_class.assert_called_once_with(None) # No custom registry
394
+ mock_repl.run.assert_called_once_with(self.backend, "test message")
395
+
396
+ @pytest.mark.asyncio
397
+ async def test_run_headless_mode_with_registry(self):
398
+ """Test run_headless_mode with custom action registry."""
399
+ custom_registry = ActionRegistry()
400
+
401
+ with patch('repl_toolkit.headless_repl.HeadlessREPL') as mock_repl_class:
402
+ mock_repl = Mock()
403
+ mock_repl.run = AsyncMock(return_value=True)
404
+ mock_repl_class.return_value = mock_repl
405
+
406
+ result = await run_headless_mode(
407
+ backend=self.backend,
408
+ action_registry=custom_registry,
409
+ initial_message="test message"
410
+ )
411
+
412
+ assert result is True
413
+ mock_repl_class.assert_called_once_with(custom_registry)
414
+ mock_repl.run.assert_called_once_with(self.backend, "test message")
415
+
416
+ @pytest.mark.asyncio
417
+ async def test_run_headless_mode_no_initial_message(self):
418
+ """Test run_headless_mode without initial message."""
419
+ with patch('repl_toolkit.headless_repl.HeadlessREPL') as mock_repl_class:
420
+ mock_repl = Mock()
421
+ mock_repl.run = AsyncMock(return_value=True)
422
+ mock_repl_class.return_value = mock_repl
423
+
424
+ result = await run_headless_mode(backend=self.backend)
425
+
426
+ assert result is True
427
+ mock_repl.run.assert_called_once_with(self.backend, None)
428
+
429
+
430
+ class TestActionIntegration:
431
+ """Test integration with action system."""
432
+
433
+ def setup_method(self):
434
+ """Set up test fixtures."""
435
+ self.backend = MockAsyncBackend()
436
+ self.executed_actions = []
437
+
438
+ # Create custom action for testing
439
+ def test_handler(context):
440
+ self.executed_actions.append({
441
+ 'name': 'test_action',
442
+ 'args': context.args,
443
+ 'triggered_by': context.triggered_by,
444
+ 'headless_mode': getattr(context, 'headless_mode', False),
445
+ 'buffer': getattr(context, 'buffer', None)
446
+ })
447
+
448
+ self.test_action = Action(
449
+ name="test_action",
450
+ description="Test action for headless",
451
+ category="Test",
452
+ handler=test_handler,
453
+ command="/test"
454
+ )
455
+
456
+ def test_action_execution_in_headless_mode(self):
457
+ """Test action execution with headless context."""
458
+ registry = ActionRegistry()
459
+ registry.register_action(self.test_action)
460
+
461
+ repl = HeadlessREPL(action_registry=registry)
462
+ repl._add_to_buffer("Buffer content")
463
+
464
+ repl._execute_command("/test arg1 arg2")
465
+
466
+ assert len(self.executed_actions) == 1
467
+ action_data = self.executed_actions[0]
468
+ assert action_data['name'] == 'test_action'
469
+ assert action_data['args'] == ['arg1', 'arg2']
470
+ assert action_data['triggered_by'] == 'command'
471
+ assert action_data['headless_mode'] is True
472
+ assert action_data['buffer'] == "Buffer content"
473
+
474
+ @pytest.mark.asyncio
475
+ async def test_action_with_buffer_manipulation(self):
476
+ """Test action that manipulates the buffer."""
477
+ def buffer_action_handler(context):
478
+ # Action that adds to buffer (if it has access)
479
+ if hasattr(context, 'buffer') and context.buffer:
480
+ # In real implementation, actions might modify buffer
481
+ # For test, just record what they received
482
+ self.executed_actions.append({
483
+ 'buffer_content': context.buffer,
484
+ 'headless_mode': getattr(context, 'headless_mode', False)
485
+ })
486
+
487
+ buffer_action = Action(
488
+ name="buffer_action",
489
+ description="Buffer manipulation action",
490
+ category="Test",
491
+ handler=buffer_action_handler,
492
+ command="/buffer"
493
+ )
494
+
495
+ registry = ActionRegistry()
496
+ registry.register_action(buffer_action)
497
+
498
+ repl = HeadlessREPL(action_registry=registry)
499
+ repl._add_to_buffer("Initial content")
500
+
501
+ repl._execute_command("/buffer")
502
+
503
+ assert len(self.executed_actions) == 1
504
+ assert self.executed_actions[0]['buffer_content'] == "Initial content"
505
+ assert self.executed_actions[0]['headless_mode'] is True
506
+
507
+ @pytest.mark.asyncio
508
+ async def test_builtin_actions_in_headless(self):
509
+ """Test built-in actions work in headless mode."""
510
+ repl = HeadlessREPL()
511
+
512
+ # Test help action (should not raise exception)
513
+ repl._execute_command("/help")
514
+
515
+ # Test shortcuts action (should not raise exception)
516
+ repl._execute_command("/shortcuts")
517
+
518
+ @pytest.mark.asyncio
519
+ async def test_shell_action_in_headless(self):
520
+ """Test shell action in headless mode."""
521
+ repl = HeadlessREPL()
522
+
523
+ # Should not raise exception
524
+ repl._execute_command("/shell echo test")
525
+
526
+
527
+ class TestErrorHandling:
528
+ """Test error handling in headless mode."""
529
+
530
+ def setup_method(self):
531
+ """Set up test fixtures."""
532
+ self.backend = MockAsyncBackend()
533
+
534
+ @pytest.mark.asyncio
535
+ async def test_backend_error_recovery(self):
536
+ """Test recovery from backend errors."""
537
+ # Backend fails on first call, succeeds on second
538
+ call_count = 0
539
+
540
+ async def failing_handle_input(user_input):
541
+ nonlocal call_count
542
+ call_count += 1
543
+ if call_count == 1:
544
+ return False # First call fails
545
+ return True # Second call succeeds
546
+
547
+ backend = Mock()
548
+ backend.handle_input = failing_handle_input
549
+
550
+ stdin_input = "Content 1\n/send\nContent 2\n/send\n"
551
+
552
+ repl = HeadlessREPL()
553
+
554
+ with patch('sys.stdin', StringIO(stdin_input)):
555
+ await repl._stdin_loop(backend)
556
+
557
+ assert repl.send_count == 2
558
+ assert repl.total_success is False # One failure makes overall false
559
+
560
+ @pytest.mark.asyncio
561
+ async def test_command_error_recovery(self):
562
+ """Test recovery from command execution errors."""
563
+ def failing_command_handler(command, **kwargs):
564
+ if "fail" in command:
565
+ raise Exception("Command failed")
566
+
567
+ repl = HeadlessREPL()
568
+ repl.action_registry.handle_command = failing_command_handler
569
+
570
+ stdin_input = "Content 1\n/fail\nContent 2\n/send\n"
571
+
572
+ with patch('sys.stdin', StringIO(stdin_input)):
573
+ await repl._stdin_loop(self.backend)
574
+
575
+ # Should continue processing despite command error
576
+ assert repl.send_count == 1
577
+ assert self.backend.inputs_received == ["Content 1\nContent 2"]
578
+
579
+ @pytest.mark.asyncio
580
+ async def test_multiple_backend_failures(self):
581
+ """Test handling multiple backend failures."""
582
+ self.backend.should_succeed = False
583
+
584
+ stdin_input = "Content 1\n/send\nContent 2\n/send\nContent 3\n"
585
+
586
+ repl = HeadlessREPL()
587
+
588
+ with patch('sys.stdin', StringIO(stdin_input)):
589
+ await repl._stdin_loop(self.backend)
590
+
591
+ assert repl.send_count == 3 # All sends attempted
592
+ assert repl.total_success is False
593
+ assert len(self.backend.inputs_received) == 3
594
+
595
+
596
+ class TestComplexScenarios:
597
+ """Test complex real-world scenarios."""
598
+
599
+ def setup_method(self):
600
+ """Set up test fixtures."""
601
+ self.backend = MockAsyncBackend()
602
+
603
+ @pytest.mark.asyncio
604
+ async def test_mixed_content_and_commands(self):
605
+ """Test mixed content and commands scenario."""
606
+ stdin_input = """First line
607
+ /help
608
+ Second line
609
+ /status
610
+ Third line
611
+ /send
612
+ Fourth line
613
+ /shell echo test
614
+ Fifth line
615
+ """
616
+
617
+ executed_commands = []
618
+
619
+ def mock_handle_command(command, **kwargs):
620
+ executed_commands.append(command)
621
+
622
+ repl = HeadlessREPL()
623
+ repl.action_registry.handle_command = mock_handle_command
624
+
625
+ with patch('sys.stdin', StringIO(stdin_input)):
626
+ await repl._stdin_loop(self.backend)
627
+
628
+ # Should have two sends: one explicit, one EOF
629
+ assert repl.send_count == 2
630
+ assert self.backend.inputs_received == [
631
+ "First line\nSecond line\nThird line",
632
+ "Fourth line\nFifth line"
633
+ ]
634
+ assert executed_commands == ["/help", "/status", "/shell echo test"]
635
+
636
+ @pytest.mark.asyncio
637
+ async def test_empty_sends_and_content(self):
638
+ """Test scenario with empty sends and content."""
639
+ stdin_input = "/send\nContent\n/send\n/send\nMore content\n"
640
+
641
+ repl = HeadlessREPL()
642
+
643
+ with patch('sys.stdin', StringIO(stdin_input)):
644
+ await repl._stdin_loop(self.backend)
645
+
646
+ # Only 2 actual sends (empty buffer sends are skipped)
647
+ assert repl.send_count == 2
648
+ assert self.backend.inputs_received == ["Content", "More content"]
649
+
650
+ @pytest.mark.asyncio
651
+ async def test_large_content_blocks(self):
652
+ """Test handling of large content blocks."""
653
+ # Create large content block
654
+ large_content = "\n".join([f"Line {i}" for i in range(1000)])
655
+ stdin_input = f"{large_content}\n/send\n"
656
+
657
+ repl = HeadlessREPL()
658
+
659
+ with patch('sys.stdin', StringIO(stdin_input)):
660
+ await repl._stdin_loop(self.backend)
661
+
662
+ assert repl.send_count == 1
663
+ assert len(self.backend.inputs_received) == 1
664
+ assert len(self.backend.inputs_received[0].split('\n')) == 1000
665
+
666
+ @pytest.mark.asyncio
667
+ async def test_rapid_send_cycles(self):
668
+ """Test rapid send cycles."""
669
+ # Multiple quick send cycles
670
+ stdin_input = ""
671
+ for i in range(10):
672
+ stdin_input += f"Content {i}\n/send\n"
673
+
674
+ repl = HeadlessREPL()
675
+
676
+ with patch('sys.stdin', StringIO(stdin_input)):
677
+ await repl._stdin_loop(self.backend)
678
+
679
+ assert repl.send_count == 10
680
+ assert len(self.backend.inputs_received) == 10
681
+ for i in range(10):
682
+ assert self.backend.inputs_received[i] == f"Content {i}"