File size: 39,381 Bytes
6b86af2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
# -*- coding: utf-8 -*-

"""
Unit tests for ThinkingParser - FSM-based parser for thinking blocks in streaming responses.

Tests cover:
- Parser state transitions (PRE_CONTENT -> IN_THINKING -> STREAMING)
- Tag detection at response start
- "Cautious" buffering for split tags
- Different handling modes (as_reasoning_content, remove, pass, strip_tags)
- Edge cases and error handling
"""

import pytest
from unittest.mock import patch

from kiro.thinking_parser import (
    ThinkingParser,
    ThinkingParseResult,
    ParserState,
)


class TestParserStateEnum:
    """Tests for ParserState enum."""
    
    def test_pre_content_value(self):
        """
        What it does: Verifies PRE_CONTENT enum value.
        Purpose: Ensure PRE_CONTENT is 0 (initial state).
        """
        print("Checking PRE_CONTENT enum value...")
        assert ParserState.PRE_CONTENT == 0
    
    def test_in_thinking_value(self):
        """
        What it does: Verifies IN_THINKING enum value.
        Purpose: Ensure IN_THINKING is 1.
        """
        print("Checking IN_THINKING enum value...")
        assert ParserState.IN_THINKING == 1
    
    def test_streaming_value(self):
        """
        What it does: Verifies STREAMING enum value.
        Purpose: Ensure STREAMING is 2.
        """
        print("Checking STREAMING enum value...")
        assert ParserState.STREAMING == 2


class TestThinkingParseResult:
    """Tests for ThinkingParseResult dataclass."""
    
    def test_default_values(self):
        """
        What it does: Verifies default values of ThinkingParseResult.
        Purpose: Ensure all fields have correct defaults.
        """
        print("Creating ThinkingParseResult with defaults...")
        result = ThinkingParseResult()
        
        print(f"Comparing: Expected None, Got {result.thinking_content}")
        assert result.thinking_content is None
        assert result.regular_content is None
        assert result.is_first_thinking_chunk is False
        assert result.is_last_thinking_chunk is False
        assert result.state_changed is False
    
    def test_custom_values(self):
        """
        What it does: Verifies custom values in ThinkingParseResult.
        Purpose: Ensure all fields can be set.
        """
        print("Creating ThinkingParseResult with custom values...")
        result = ThinkingParseResult(
            thinking_content="thinking",
            regular_content="regular",
            is_first_thinking_chunk=True,
            is_last_thinking_chunk=True,
            state_changed=True
        )
        
        print(f"Comparing thinking_content: Expected 'thinking', Got '{result.thinking_content}'")
        assert result.thinking_content == "thinking"
        assert result.regular_content == "regular"
        assert result.is_first_thinking_chunk is True
        assert result.is_last_thinking_chunk is True
        assert result.state_changed is True


class TestThinkingParserInitialization:
    """Tests for ThinkingParser initialization."""
    
    def test_default_initialization(self):
        """
        What it does: Verifies default initialization of ThinkingParser.
        Purpose: Ensure parser starts in PRE_CONTENT state with empty buffers.
        """
        print("Creating ThinkingParser with defaults...")
        parser = ThinkingParser()
        
        print(f"Comparing state: Expected PRE_CONTENT, Got {parser.state}")
        assert parser.state == ParserState.PRE_CONTENT
        assert parser.initial_buffer == ""
        assert parser.thinking_buffer == ""
        assert parser.open_tag is None
        assert parser.close_tag is None
        assert parser.is_first_thinking_chunk is True
        assert parser._thinking_block_found is False
    
    def test_custom_handling_mode(self):
        """
        What it does: Verifies custom handling_mode parameter.
        Purpose: Ensure handling_mode can be overridden.
        """
        print("Creating ThinkingParser with custom handling_mode...")
        parser = ThinkingParser(handling_mode="remove")
        
        print(f"Comparing handling_mode: Expected 'remove', Got '{parser.handling_mode}'")
        assert parser.handling_mode == "remove"
    
    def test_custom_open_tags(self):
        """
        What it does: Verifies custom open_tags parameter.
        Purpose: Ensure open_tags can be overridden.
        """
        print("Creating ThinkingParser with custom open_tags...")
        custom_tags = ["<custom>", "<test>"]
        parser = ThinkingParser(open_tags=custom_tags)
        
        print(f"Comparing open_tags: Expected {custom_tags}, Got {parser.open_tags}")
        assert parser.open_tags == custom_tags
    
    def test_custom_initial_buffer_size(self):
        """
        What it does: Verifies custom initial_buffer_size parameter.
        Purpose: Ensure initial_buffer_size can be overridden.
        """
        print("Creating ThinkingParser with custom initial_buffer_size...")
        parser = ThinkingParser(initial_buffer_size=50)
        
        print(f"Comparing initial_buffer_size: Expected 50, Got {parser.initial_buffer_size}")
        assert parser.initial_buffer_size == 50
    
    def test_max_tag_length_calculated(self):
        """
        What it does: Verifies max_tag_length is calculated from open_tags.
        Purpose: Ensure cautious buffering uses correct buffer size.
        """
        print("Creating ThinkingParser and checking max_tag_length...")
        parser = ThinkingParser(open_tags=["<thinking>", "<think>"])
        
        # max_tag_length = max(len(tag) for tag in open_tags) * 2
        # len("<thinking>") = 10, so max_tag_length = 20
        expected = 20
        print(f"Comparing max_tag_length: Expected {expected}, Got {parser.max_tag_length}")
        assert parser.max_tag_length == expected


class TestThinkingParserFeedPreContent:
    """Tests for ThinkingParser.feed() in PRE_CONTENT state."""
    
    def test_empty_content_returns_empty_result(self):
        """
        What it does: Verifies empty content returns empty result.
        Purpose: Ensure empty string doesn't change state.
        """
        print("Feeding empty content...")
        parser = ThinkingParser()
        result = parser.feed("")
        
        print(f"Comparing result: Expected empty result")
        assert result.thinking_content is None
        assert result.regular_content is None
        assert result.state_changed is False
        assert parser.state == ParserState.PRE_CONTENT
    
    def test_detects_thinking_tag(self):
        """
        What it does: Verifies <thinking> tag detection.
        Purpose: Ensure parser transitions to IN_THINKING on tag detection.
        """
        print("Feeding content with <thinking> tag...")
        parser = ThinkingParser()
        result = parser.feed("<thinking>Hello")
        
        print(f"Comparing state: Expected IN_THINKING, Got {parser.state}")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<thinking>"
        assert parser.close_tag == "</thinking>"
        assert result.state_changed is True
        assert parser._thinking_block_found is True
    
    def test_detects_think_tag(self):
        """
        What it does: Verifies <think> tag detection.
        Purpose: Ensure parser detects alternative tag format.
        """
        print("Feeding content with <think> tag...")
        parser = ThinkingParser()
        result = parser.feed("<think>Hello")
        
        print(f"Comparing open_tag: Expected '<think>', Got '{parser.open_tag}'")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<think>"
        assert parser.close_tag == "</think>"
    
    def test_detects_reasoning_tag(self):
        """
        What it does: Verifies <reasoning> tag detection.
        Purpose: Ensure parser detects reasoning tag format.
        """
        print("Feeding content with <reasoning> tag...")
        parser = ThinkingParser()
        result = parser.feed("<reasoning>Hello")
        
        print(f"Comparing open_tag: Expected '<reasoning>', Got '{parser.open_tag}'")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<reasoning>"
        assert parser.close_tag == "</reasoning>"
    
    def test_detects_thought_tag(self):
        """
        What it does: Verifies <thought> tag detection.
        Purpose: Ensure parser detects thought tag format.
        """
        print("Feeding content with <thought> tag...")
        parser = ThinkingParser()
        result = parser.feed("<thought>Hello")
        
        print(f"Comparing open_tag: Expected '<thought>', Got '{parser.open_tag}'")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<thought>"
        assert parser.close_tag == "</thought>"
    
    def test_strips_leading_whitespace_for_tag_detection(self):
        """
        What it does: Verifies leading whitespace is stripped for tag detection.
        Purpose: Ensure tags with leading whitespace are detected.
        """
        print("Feeding content with leading whitespace...")
        parser = ThinkingParser()
        result = parser.feed("  \n\n<thinking>Hello")
        
        print(f"Comparing state: Expected IN_THINKING, Got {parser.state}")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<thinking>"
    
    def test_buffers_partial_tag(self):
        """
        What it does: Verifies partial tag is buffered.
        Purpose: Ensure parser waits for complete tag.
        """
        print("Feeding partial tag...")
        parser = ThinkingParser()
        result = parser.feed("<think")
        
        print(f"Comparing state: Expected PRE_CONTENT, Got {parser.state}")
        assert parser.state == ParserState.PRE_CONTENT
        assert parser.initial_buffer == "<think"
        assert result.state_changed is False
    
    def test_completes_partial_tag(self):
        """
        What it does: Verifies partial tag is completed across chunks.
        Purpose: Ensure split tags are handled correctly.
        """
        print("Feeding partial tag in two chunks...")
        parser = ThinkingParser()
        
        result1 = parser.feed("<think")
        print(f"After first chunk: state={parser.state}, buffer='{parser.initial_buffer}'")
        assert parser.state == ParserState.PRE_CONTENT
        
        result2 = parser.feed("ing>Hello")
        print(f"After second chunk: state={parser.state}")
        assert parser.state == ParserState.IN_THINKING
        assert parser.open_tag == "<thinking>"
    
    def test_no_tag_transitions_to_streaming(self):
        """
        What it does: Verifies transition to STREAMING when no tag found.
        Purpose: Ensure regular content is passed through.
        """
        print("Feeding content without thinking tag...")
        parser = ThinkingParser()
        result = parser.feed("Hello, this is regular content without any thinking tags.")
        
        print(f"Comparing state: Expected STREAMING, Got {parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result.state_changed is True
        assert result.regular_content == "Hello, this is regular content without any thinking tags."
    
    def test_buffer_exceeds_limit_transitions_to_streaming(self):
        """
        What it does: Verifies transition to STREAMING when buffer exceeds limit.
        Purpose: Ensure parser doesn't buffer indefinitely.
        """
        print("Feeding content that exceeds buffer limit...")
        parser = ThinkingParser(initial_buffer_size=10)
        result = parser.feed("This is a long content that exceeds the buffer limit")
        
        print(f"Comparing state: Expected STREAMING, Got {parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result.state_changed is True


class TestThinkingParserFeedInThinking:
    """Tests for ThinkingParser.feed() in IN_THINKING state."""
    
    def test_accumulates_thinking_content(self):
        """
        What it does: Verifies thinking content is accumulated.
        Purpose: Ensure content inside thinking block is captured.
        """
        print("Feeding thinking content...")
        parser = ThinkingParser()
        parser.feed("<thinking>")
        
        # Feed more content
        result = parser.feed("This is thinking content")
        
        print(f"Comparing thinking_buffer: Got '{parser.thinking_buffer}'")
        # Content is in buffer due to cautious sending
        assert "This is thinking content" in parser.thinking_buffer or result.thinking_content
    
    def test_detects_closing_tag(self):
        """
        What it does: Verifies closing tag detection.
        Purpose: Ensure parser transitions to STREAMING on closing tag.
        """
        print("Feeding content with closing tag...")
        parser = ThinkingParser()
        parser.feed("<thinking>Hello")
        result = parser.feed("</thinking>World")
        
        print(f"Comparing state: Expected STREAMING, Got {parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result.is_last_thinking_chunk is True
        assert result.state_changed is True
    
    def test_regular_content_after_closing_tag(self):
        """
        What it does: Verifies regular content after closing tag.
        Purpose: Ensure content after closing tag is returned as regular_content.
        """
        print("Feeding content with closing tag and regular content...")
        parser = ThinkingParser()
        parser.feed("<thinking>Thinking")
        result = parser.feed("</thinking>Regular content")
        
        print(f"Comparing regular_content: Got '{result.regular_content}'")
        assert result.regular_content == "Regular content"
    
    def test_strips_whitespace_after_closing_tag(self):
        """
        What it does: Verifies whitespace is stripped after closing tag.
        Purpose: Ensure leading newlines after closing tag are removed.
        """
        print("Feeding content with whitespace after closing tag...")
        parser = ThinkingParser()
        parser.feed("<thinking>Thinking")
        result = parser.feed("</thinking>\n\nRegular content")
        
        print(f"Comparing regular_content: Got '{result.regular_content}'")
        assert result.regular_content == "Regular content"
    
    def test_cautious_buffering(self):
        """
        What it does: Verifies cautious buffering keeps last max_tag_length chars.
        Purpose: Ensure closing tag is not split across chunks.
        """
        print("Testing cautious buffering...")
        parser = ThinkingParser(open_tags=["<t>"])  # Short tag for easier testing
        parser.feed("<t>")
        
        # Feed content longer than max_tag_length
        long_content = "A" * 50
        result = parser.feed(long_content)
        
        print(f"Comparing thinking_buffer length: Got {len(parser.thinking_buffer)}")
        # Buffer should keep last max_tag_length chars
        assert len(parser.thinking_buffer) <= parser.max_tag_length
    
    def test_split_closing_tag(self):
        """
        What it does: Verifies split closing tag is handled.
        Purpose: Ensure closing tag split across chunks is detected.
        """
        print("Feeding split closing tag...")
        parser = ThinkingParser()
        parser.feed("<thinking>Hello")
        parser.feed("</think")
        result = parser.feed("ing>World")
        
        print(f"Comparing state: Expected STREAMING, Got {parser.state}")
        assert parser.state == ParserState.STREAMING


class TestThinkingParserFeedStreaming:
    """Tests for ThinkingParser.feed() in STREAMING state."""
    
    def test_passes_content_through(self):
        """
        What it does: Verifies content is passed through in STREAMING state.
        Purpose: Ensure regular content is returned as-is.
        """
        print("Feeding content in STREAMING state...")
        parser = ThinkingParser()
        # Transition to STREAMING by feeding non-tag content
        parser.feed("Regular content")
        
        result = parser.feed("More content")
        
        print(f"Comparing regular_content: Expected 'More content', Got '{result.regular_content}'")
        assert result.regular_content == "More content"
        assert result.thinking_content is None
    
    def test_ignores_thinking_tags_in_streaming(self):
        """
        What it does: Verifies thinking tags are ignored in STREAMING state.
        Purpose: Ensure tags after initial detection are passed through.
        """
        print("Feeding thinking tag in STREAMING state...")
        parser = ThinkingParser()
        parser.feed("Regular content")  # Transition to STREAMING
        
        result = parser.feed("<thinking>This should be regular</thinking>")
        
        print(f"Comparing regular_content: Got '{result.regular_content}'")
        assert result.regular_content == "<thinking>This should be regular</thinking>"
        assert result.thinking_content is None


class TestThinkingParserFinalize:
    """Tests for ThinkingParser.finalize()."""
    
    def test_flushes_thinking_buffer(self):
        """
        What it does: Verifies thinking buffer is flushed on finalize.
        Purpose: Ensure remaining thinking content is returned.
        """
        print("Finalizing parser with thinking buffer...")
        parser = ThinkingParser()
        parser.feed("<thinking>Incomplete thinking")
        
        result = parser.finalize()
        
        print(f"Comparing thinking_content: Got '{result.thinking_content}'")
        assert result.thinking_content is not None
        assert result.is_last_thinking_chunk is True
    
    def test_flushes_initial_buffer(self):
        """
        What it does: Verifies initial buffer is flushed on finalize.
        Purpose: Ensure buffered content is returned when no tag found.
        """
        print("Finalizing parser with initial buffer...")
        parser = ThinkingParser()
        parser.feed("<thi")  # Partial tag, stays in initial_buffer
        
        result = parser.finalize()
        
        print(f"Comparing regular_content: Got '{result.regular_content}'")
        assert result.regular_content == "<thi"
    
    def test_clears_buffers_after_finalize(self):
        """
        What it does: Verifies buffers are cleared after finalize.
        Purpose: Ensure buffers are empty after finalize.
        """
        print("Checking buffers after finalize...")
        parser = ThinkingParser()
        parser.feed("<thinking>Content")
        parser.finalize()
        
        print(f"Comparing buffers: thinking_buffer='{parser.thinking_buffer}', initial_buffer='{parser.initial_buffer}'")
        assert parser.thinking_buffer == ""
        assert parser.initial_buffer == ""


class TestThinkingParserReset:
    """Tests for ThinkingParser.reset()."""
    
    def test_resets_to_initial_state(self):
        """
        What it does: Verifies reset returns parser to initial state.
        Purpose: Ensure parser can be reused.
        """
        print("Resetting parser after use...")
        parser = ThinkingParser()
        parser.feed("<thinking>Content</thinking>Regular")
        
        parser.reset()
        
        print(f"Comparing state: Expected PRE_CONTENT, Got {parser.state}")
        assert parser.state == ParserState.PRE_CONTENT
        assert parser.initial_buffer == ""
        assert parser.thinking_buffer == ""
        assert parser.open_tag is None
        assert parser.close_tag is None
        assert parser.is_first_thinking_chunk is True
        assert parser._thinking_block_found is False


class TestThinkingParserFoundThinkingBlock:
    """Tests for ThinkingParser.found_thinking_block property."""
    
    def test_false_initially(self):
        """
        What it does: Verifies found_thinking_block is False initially.
        Purpose: Ensure property starts as False.
        """
        print("Checking found_thinking_block initially...")
        parser = ThinkingParser()
        
        print(f"Comparing: Expected False, Got {parser.found_thinking_block}")
        assert parser.found_thinking_block is False
    
    def test_true_after_tag_detection(self):
        """
        What it does: Verifies found_thinking_block is True after tag detection.
        Purpose: Ensure property is set when thinking block is found.
        """
        print("Checking found_thinking_block after tag detection...")
        parser = ThinkingParser()
        parser.feed("<thinking>Content")
        
        print(f"Comparing: Expected True, Got {parser.found_thinking_block}")
        assert parser.found_thinking_block is True
    
    def test_false_when_no_tag(self):
        """
        What it does: Verifies found_thinking_block is False when no tag found.
        Purpose: Ensure property stays False for regular content.
        """
        print("Checking found_thinking_block with no tag...")
        parser = ThinkingParser()
        parser.feed("Regular content without thinking tags")
        
        print(f"Comparing: Expected False, Got {parser.found_thinking_block}")
        assert parser.found_thinking_block is False


class TestThinkingParserProcessForOutput:
    """Tests for ThinkingParser.process_for_output()."""
    
    def test_as_reasoning_content_mode(self):
        """
        What it does: Verifies as_reasoning_content mode returns content as-is.
        Purpose: Ensure content is returned unchanged for reasoning_content field.
        """
        print("Testing as_reasoning_content mode...")
        parser = ThinkingParser(handling_mode="as_reasoning_content")
        parser.open_tag = "<thinking>"
        parser.close_tag = "</thinking>"
        
        result = parser.process_for_output("Thinking content", is_first=True, is_last=True)
        
        print(f"Comparing: Expected 'Thinking content', Got '{result}'")
        assert result == "Thinking content"
    
    def test_remove_mode(self):
        """
        What it does: Verifies remove mode returns None.
        Purpose: Ensure thinking content is removed.
        """
        print("Testing remove mode...")
        parser = ThinkingParser(handling_mode="remove")
        
        result = parser.process_for_output("Thinking content", is_first=True, is_last=True)
        
        print(f"Comparing: Expected None, Got {result}")
        assert result is None
    
    def test_pass_mode_first_chunk(self):
        """
        What it does: Verifies pass mode adds opening tag to first chunk.
        Purpose: Ensure tags are preserved in pass mode.
        """
        print("Testing pass mode with first chunk...")
        parser = ThinkingParser(handling_mode="pass")
        parser.open_tag = "<thinking>"
        parser.close_tag = "</thinking>"
        
        result = parser.process_for_output("Content", is_first=True, is_last=False)
        
        print(f"Comparing: Expected '<thinking>Content', Got '{result}'")
        assert result == "<thinking>Content"
    
    def test_pass_mode_last_chunk(self):
        """
        What it does: Verifies pass mode adds closing tag to last chunk.
        Purpose: Ensure closing tag is added in pass mode.
        """
        print("Testing pass mode with last chunk...")
        parser = ThinkingParser(handling_mode="pass")
        parser.open_tag = "<thinking>"
        parser.close_tag = "</thinking>"
        
        result = parser.process_for_output("Content", is_first=False, is_last=True)
        
        print(f"Comparing: Expected 'Content</thinking>', Got '{result}'")
        assert result == "Content</thinking>"
    
    def test_pass_mode_first_and_last_chunk(self):
        """
        What it does: Verifies pass mode adds both tags when first and last.
        Purpose: Ensure both tags are added for single chunk.
        """
        print("Testing pass mode with first and last chunk...")
        parser = ThinkingParser(handling_mode="pass")
        parser.open_tag = "<thinking>"
        parser.close_tag = "</thinking>"
        
        result = parser.process_for_output("Content", is_first=True, is_last=True)
        
        print(f"Comparing: Expected '<thinking>Content</thinking>', Got '{result}'")
        assert result == "<thinking>Content</thinking>"
    
    def test_pass_mode_middle_chunk(self):
        """
        What it does: Verifies pass mode returns content as-is for middle chunk.
        Purpose: Ensure no tags are added for middle chunks.
        """
        print("Testing pass mode with middle chunk...")
        parser = ThinkingParser(handling_mode="pass")
        parser.open_tag = "<thinking>"
        parser.close_tag = "</thinking>"
        
        result = parser.process_for_output("Content", is_first=False, is_last=False)
        
        print(f"Comparing: Expected 'Content', Got '{result}'")
        assert result == "Content"
    
    def test_strip_tags_mode(self):
        """
        What it does: Verifies strip_tags mode returns content without tags.
        Purpose: Ensure content is returned without tags.
        """
        print("Testing strip_tags mode...")
        parser = ThinkingParser(handling_mode="strip_tags")
        
        result = parser.process_for_output("Thinking content", is_first=True, is_last=True)
        
        print(f"Comparing: Expected 'Thinking content', Got '{result}'")
        assert result == "Thinking content"
    
    def test_none_content_returns_none(self):
        """
        What it does: Verifies None content returns None.
        Purpose: Ensure None is handled correctly.
        """
        print("Testing None content...")
        parser = ThinkingParser()
        
        result = parser.process_for_output(None, is_first=True, is_last=True)
        
        print(f"Comparing: Expected None, Got {result}")
        assert result is None
    
    def test_empty_content_returns_none(self):
        """
        What it does: Verifies empty content returns None.
        Purpose: Ensure empty string is handled correctly.
        """
        print("Testing empty content...")
        parser = ThinkingParser()
        
        result = parser.process_for_output("", is_first=True, is_last=True)
        
        print(f"Comparing: Expected None, Got {result}")
        assert result is None


class TestThinkingParserFullFlow:
    """Integration tests for full parsing flow."""
    
    def test_complete_thinking_block(self):
        """
        What it does: Verifies complete thinking block parsing.
        Purpose: Ensure full flow works correctly.
        """
        print("Testing complete thinking block flow...")
        parser = ThinkingParser()
        
        # Feed complete thinking block
        result1 = parser.feed("<thinking>This is my reasoning process.</thinking>Here is the answer.")
        
        print(f"State: {parser.state}")
        print(f"Thinking content: {result1.thinking_content}")
        print(f"Regular content: {result1.regular_content}")
        
        assert parser.state == ParserState.STREAMING
        assert parser.found_thinking_block is True
        assert result1.regular_content == "Here is the answer."
    
    def test_multi_chunk_thinking_block(self):
        """
        What it does: Verifies thinking block split across multiple chunks.
        Purpose: Ensure chunked content is handled correctly.
        """
        print("Testing multi-chunk thinking block...")
        parser = ThinkingParser()
        
        # Feed in multiple chunks
        result1 = parser.feed("<think")
        print(f"After chunk 1: state={parser.state}")
        assert parser.state == ParserState.PRE_CONTENT
        
        result2 = parser.feed("ing>Let me think")
        print(f"After chunk 2: state={parser.state}")
        assert parser.state == ParserState.IN_THINKING
        
        result3 = parser.feed(" about this...</think")
        print(f"After chunk 3: state={parser.state}")
        assert parser.state == ParserState.IN_THINKING
        
        result4 = parser.feed("ing>The answer is 42.")
        print(f"After chunk 4: state={parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result4.regular_content == "The answer is 42."
    
    def test_no_thinking_block(self):
        """
        What it does: Verifies handling of content without thinking block.
        Purpose: Ensure regular content passes through unchanged.
        """
        print("Testing content without thinking block...")
        parser = ThinkingParser()
        
        result = parser.feed("This is just regular content without any thinking tags.")
        
        print(f"State: {parser.state}")
        print(f"Regular content: {result.regular_content}")
        
        assert parser.state == ParserState.STREAMING
        assert parser.found_thinking_block is False
        assert result.regular_content == "This is just regular content without any thinking tags."
    
    def test_thinking_block_with_newlines(self):
        """
        What it does: Verifies thinking block with newlines after closing tag.
        Purpose: Ensure newlines are stripped from regular content.
        """
        print("Testing thinking block with newlines...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking>Reasoning</thinking>\n\n\nAnswer here")
        
        print(f"Regular content: '{result.regular_content}'")
        assert result.regular_content == "Answer here"
    
    def test_empty_thinking_block(self):
        """
        What it does: Verifies empty thinking block handling.
        Purpose: Ensure empty thinking block doesn't break parser.
        """
        print("Testing empty thinking block...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking></thinking>Answer")
        
        print(f"State: {parser.state}")
        print(f"Regular content: '{result.regular_content}'")
        assert parser.state == ParserState.STREAMING
        assert result.regular_content == "Answer"
    
    def test_thinking_block_only_whitespace_after(self):
        """
        What it does: Verifies thinking block with only whitespace after closing tag.
        Purpose: Ensure whitespace-only content after tag returns None.
        """
        print("Testing thinking block with only whitespace after...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking>Reasoning</thinking>   \n\n   ")
        
        print(f"Regular content: {result.regular_content}")
        # Whitespace-only content should be stripped to None
        assert result.regular_content is None or result.regular_content == ""


class TestThinkingParserEdgeCases:
    """Edge case tests for ThinkingParser."""
    
    def test_nested_tags_not_supported(self):
        """
        What it does: Verifies nested tags are not specially handled.
        Purpose: Ensure nested tags are treated as content.
        """
        print("Testing nested tags...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking>Outer<thinking>Inner</thinking>Still outer</thinking>Answer")
        
        print(f"State: {parser.state}")
        # First </thinking> closes the block
        assert parser.state == ParserState.STREAMING
    
    def test_tag_in_middle_of_content(self):
        """
        What it does: Verifies tag in middle of content is not detected.
        Purpose: Ensure tags are only detected at start.
        """
        print("Testing tag in middle of content...")
        parser = ThinkingParser()
        
        result = parser.feed("Some text <thinking>This is not a thinking block</thinking>")
        
        print(f"State: {parser.state}")
        print(f"Regular content: '{result.regular_content}'")
        assert parser.state == ParserState.STREAMING
        assert parser.found_thinking_block is False
        assert "<thinking>" in result.regular_content
    
    def test_malformed_closing_tag(self):
        """
        What it does: Verifies malformed closing tag is not detected.
        Purpose: Ensure only exact closing tag is matched.
        """
        print("Testing malformed closing tag...")
        parser = ThinkingParser()
        
        parser.feed("<thinking>Content")
        result = parser.feed("</THINKING>More content")  # Wrong case
        
        print(f"State: {parser.state}")
        # Should still be in thinking state
        assert parser.state == ParserState.IN_THINKING
    
    def test_unicode_content(self):
        """
        What it does: Verifies Unicode content is handled correctly.
        Purpose: Ensure non-ASCII characters work.
        """
        print("Testing Unicode content...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking>Думаю о проблеме 🤔</thinking>Ответ: 42")
        
        print(f"Regular content: '{result.regular_content}'")
        assert parser.state == ParserState.STREAMING
        assert result.regular_content == "Ответ: 42"
    
    def test_very_long_thinking_content(self):
        """
        What it does: Verifies very long thinking content is handled.
        Purpose: Ensure large content doesn't break parser.
        """
        print("Testing very long thinking content...")
        parser = ThinkingParser()
        
        long_content = "A" * 10000
        result = parser.feed(f"<thinking>{long_content}</thinking>Done")
        
        print(f"State: {parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result.regular_content == "Done"
    
    def test_special_characters_in_content(self):
        """
        What it does: Verifies special characters are handled.
        Purpose: Ensure HTML-like content doesn't break parser.
        """
        print("Testing special characters...")
        parser = ThinkingParser()
        
        result = parser.feed("<thinking>Content with <b>bold</b> and &amp; entities</thinking>Answer")
        
        print(f"State: {parser.state}")
        assert parser.state == ParserState.STREAMING
        assert result.regular_content == "Answer"
    
    def test_multiple_feeds_after_streaming(self):
        """
        What it does: Verifies multiple feeds in STREAMING state.
        Purpose: Ensure parser continues to work after transition.
        """
        print("Testing multiple feeds in STREAMING state...")
        parser = ThinkingParser()
        
        parser.feed("<thinking>Thinking</thinking>First")
        result2 = parser.feed(" Second")
        result3 = parser.feed(" Third")
        
        print(f"Result 2: '{result2.regular_content}'")
        print(f"Result 3: '{result3.regular_content}'")
        assert result2.regular_content == " Second"
        assert result3.regular_content == " Third"


class TestThinkingParserConfigIntegration:
    """Tests for ThinkingParser integration with config."""
    
    def test_uses_config_handling_mode(self):
        """
        What it does: Verifies parser uses FAKE_REASONING_HANDLING from config.
        Purpose: Ensure config integration works.
        """
        print("Testing config handling mode...")
        with patch('kiro.thinking_parser.FAKE_REASONING_HANDLING', 'remove'):
            parser = ThinkingParser()
            
            print(f"Handling mode: {parser.handling_mode}")
            assert parser.handling_mode == "remove"
    
    def test_uses_config_open_tags(self):
        """
        What it does: Verifies parser uses FAKE_REASONING_OPEN_TAGS from config.
        Purpose: Ensure config integration works.
        """
        print("Testing config open tags...")
        custom_tags = ["<custom>"]
        with patch('kiro.thinking_parser.FAKE_REASONING_OPEN_TAGS', custom_tags):
            parser = ThinkingParser()
            
            print(f"Open tags: {parser.open_tags}")
            assert parser.open_tags == custom_tags
    
    def test_default_initial_buffer_size_from_config(self):
        """
        What it does: Verifies parser uses default initial_buffer_size from config.
        Purpose: Ensure config value is used when not overridden.
        
        Note: We can't easily patch the config value after import, so we just
        verify the default is used. Custom values are tested in
        TestThinkingParserInitialization.test_custom_initial_buffer_size.
        """
        print("Testing default initial buffer size from config...")
        from kiro.config import FAKE_REASONING_INITIAL_BUFFER_SIZE
        
        parser = ThinkingParser()
        
        print(f"Initial buffer size: {parser.initial_buffer_size}")
        print(f"Config value: {FAKE_REASONING_INITIAL_BUFFER_SIZE}")
        assert parser.initial_buffer_size == FAKE_REASONING_INITIAL_BUFFER_SIZE


class TestInjectThinkingTags:
    """Tests for inject_thinking_tags function in converters."""
    
    def test_injects_tags_when_enabled(self):
        """
        What it does: Verifies tags are injected when FAKE_REASONING_ENABLED is True.
        Purpose: Ensure tags are added to content.
        """
        print("Testing tag injection when enabled...")
        from kiro.converters_core import inject_thinking_tags
        
        with patch('kiro.converters_core.FAKE_REASONING_ENABLED', True):
            with patch('kiro.converters_core.FAKE_REASONING_MAX_TOKENS', 4000):
                result = inject_thinking_tags("Hello")
        
        print(f"Result: '{result}'")
        assert "<thinking_mode>enabled</thinking_mode>" in result
        assert "<max_thinking_length>4000</max_thinking_length>" in result
        assert "Hello" in result
    
    def test_no_injection_when_disabled(self):
        """
        What it does: Verifies tags are not injected when FAKE_REASONING_ENABLED is False.
        Purpose: Ensure tags are not added when disabled.
        """
        print("Testing no tag injection when disabled...")
        from kiro.converters_core import inject_thinking_tags
        
        with patch('kiro.converters_core.FAKE_REASONING_ENABLED', False):
            result = inject_thinking_tags("Hello")
        
        print(f"Result: '{result}'")
        assert result == "Hello"
        assert "<thinking_mode>" not in result
    
    def test_injection_preserves_content(self):
        """
        What it does: Verifies original content is preserved after injection.
        Purpose: Ensure content is not modified.
        """
        print("Testing content preservation...")
        from kiro.converters_core import inject_thinking_tags
        
        original = "This is my original content with special chars: <>&"
        
        with patch('kiro.converters_core.FAKE_REASONING_ENABLED', True):
            with patch('kiro.converters_core.FAKE_REASONING_MAX_TOKENS', 4000):
                result = inject_thinking_tags(original)
        
        print(f"Result ends with original: {result.endswith(original)}")
        assert result.endswith(original)