File size: 11,284 Bytes
38c016b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
Comprehensive test for bounds checking fixes in crossword generator.
"""

import asyncio
import sys
import pytest
from pathlib import Path

# Add project root to path
project_root = Path(__file__).parent.parent  # Go up from test-integration to backend-py
sys.path.insert(0, str(project_root))

from src.services.crossword_generator_fixed import CrosswordGeneratorFixed

class TestBoundsChecking:
    """Test all bounds checking in crossword generator."""
    
    def setup_method(self):
        """Setup test instance."""
        self.generator = CrosswordGeneratorFixed(vector_service=None)
    
    def test_can_place_word_bounds_horizontal(self):
        """Test _can_place_word bounds checking for horizontal placement."""
        # Create small grid
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Test cases that should fail bounds checking
        assert not self.generator._can_place_word(grid, "TOOLONG", 2, 1, "horizontal")  # Word too long
        assert not self.generator._can_place_word(grid, "TEST", -1, 1, "horizontal")    # Negative row
        assert not self.generator._can_place_word(grid, "TEST", 1, -1, "horizontal")    # Negative col
        assert not self.generator._can_place_word(grid, "TEST", 5, 1, "horizontal")     # Row >= size
        assert not self.generator._can_place_word(grid, "TEST", 1, 5, "horizontal")     # Col >= size
        assert not self.generator._can_place_word(grid, "TEST", 1, 3, "horizontal")     # Word extends beyond grid
        
        # Test cases that should pass
        assert self.generator._can_place_word(grid, "TEST", 2, 1, "horizontal")         # Valid placement
        assert self.generator._can_place_word(grid, "A", 0, 0, "horizontal")            # Single letter
        
    def test_can_place_word_bounds_vertical(self):
        """Test _can_place_word bounds checking for vertical placement."""
        # Create small grid
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Test cases that should fail bounds checking
        assert not self.generator._can_place_word(grid, "TOOLONG", 1, 2, "vertical")    # Word too long
        assert not self.generator._can_place_word(grid, "TEST", -1, 1, "vertical")      # Negative row
        assert not self.generator._can_place_word(grid, "TEST", 1, -1, "vertical")      # Negative col
        assert not self.generator._can_place_word(grid, "TEST", 5, 1, "vertical")       # Row >= size
        assert not self.generator._can_place_word(grid, "TEST", 1, 5, "vertical")       # Col >= size
        assert not self.generator._can_place_word(grid, "TEST", 3, 1, "vertical")       # Word extends beyond grid
        
        # Test cases that should pass
        assert self.generator._can_place_word(grid, "TEST", 1, 2, "vertical")           # Valid placement
        assert self.generator._can_place_word(grid, "A", 0, 0, "vertical")              # Single letter
    
    def test_place_word_bounds_horizontal(self):
        """Test _place_word bounds checking for horizontal placement."""
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Valid placement should work
        original_state = self.generator._place_word(grid, "TEST", 2, 1, "horizontal")
        assert len(original_state) == 4
        assert grid[2][1] == "T"
        assert grid[2][4] == "T"
        
        # Test out-of-bounds placement should raise IndexError
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TOOLONG", 2, 1, "horizontal")
        
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", -1, 1, "horizontal")
        
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", 5, 1, "horizontal")
            
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", 1, 5, "horizontal")
    
    def test_place_word_bounds_vertical(self):
        """Test _place_word bounds checking for vertical placement."""
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Valid placement should work
        original_state = self.generator._place_word(grid, "TEST", 1, 2, "vertical")
        assert len(original_state) == 4
        assert grid[1][2] == "T"
        assert grid[4][2] == "T"
        
        # Test out-of-bounds placement should raise IndexError
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TOOLONG", 1, 2, "vertical")
            
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", -1, 2, "vertical")
            
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", 5, 2, "vertical")
            
        with pytest.raises(IndexError):
            self.generator._place_word(grid, "TEST", 2, 5, "vertical")
    
    def test_remove_word_bounds(self):
        """Test _remove_word bounds checking."""
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Place a word first
        original_state = self.generator._place_word(grid, "TEST", 2, 1, "horizontal")
        
        # Normal removal should work
        self.generator._remove_word(grid, original_state)
        assert grid[2][1] == "."
        
        # Test invalid original state should raise IndexError
        bad_state = [{"row": -1, "col": 1, "value": "."}]
        with pytest.raises(IndexError):
            self.generator._remove_word(grid, bad_state)
            
        bad_state = [{"row": 5, "col": 1, "value": "."}]
        with pytest.raises(IndexError):
            self.generator._remove_word(grid, bad_state)
            
        bad_state = [{"row": 1, "col": -1, "value": "."}]
        with pytest.raises(IndexError):
            self.generator._remove_word(grid, bad_state)
            
        bad_state = [{"row": 1, "col": 5, "value": "."}]
        with pytest.raises(IndexError):
            self.generator._remove_word(grid, bad_state)
    
    def test_create_simple_cross_bounds(self):
        """Test _create_simple_cross bounds checking."""
        # Test with words that have intersections
        word_list = ["CAT", "TOY"]  # 'T' intersection
        word_objs = [{"word": w, "clue": f"Clue for {w}"} for w in word_list]
        
        # This should work without bounds errors
        result = self.generator._create_simple_cross(word_list, word_objs)
        assert result is not None
        assert len(result["placed_words"]) == 2
        
        # Test with words that might cause issues
        word_list = ["A", "A"]  # Same single letter
        word_objs = [{"word": w, "clue": f"Clue for {w}"} for w in word_list]
        
        # This should not crash with bounds errors
        result = self.generator._create_simple_cross(word_list, word_objs)
        # May return None due to placement issues, but should not crash
    
    def test_trim_grid_bounds(self):
        """Test _trim_grid bounds checking."""
        # Create a grid with words placed
        grid = [["." for _ in range(10)] for _ in range(10)]
        
        # Place some letters
        grid[5][3] = "T"
        grid[5][4] = "E"
        grid[5][5] = "S"
        grid[5][6] = "T"
        
        placed_words = [{
            "word": "TEST",
            "row": 5,
            "col": 3,
            "direction": "horizontal",
            "number": 1
        }]
        
        # This should work without bounds errors
        result = self.generator._trim_grid(grid, placed_words)
        assert result is not None
        assert "grid" in result
        assert "placed_words" in result
        
        # Test with edge case placements
        placed_words = [{
            "word": "A",
            "row": 0,
            "col": 0,
            "direction": "horizontal", 
            "number": 1
        }]
        
        grid[0][0] = "A"
        result = self.generator._trim_grid(grid, placed_words)
        assert result is not None
    
    def test_calculation_placement_score_bounds(self):
        """Test _calculate_placement_score bounds checking."""
        grid = [["." for _ in range(5)] for _ in range(5)]
        
        # Place some letters for intersection testing
        grid[2][2] = "T"
        grid[2][3] = "E"
        
        placement = {"row": 2, "col": 2, "direction": "horizontal"}
        placed_words = []
        
        # This should work without bounds errors
        score = self.generator._calculate_placement_score(grid, "TEST", placement, placed_words)
        assert isinstance(score, int)
        
        # Test with out-of-bounds placement (should handle gracefully)
        placement = {"row": 4, "col": 3, "direction": "horizontal"}  # Would extend beyond grid
        score = self.generator._calculate_placement_score(grid, "TEST", placement, placed_words)
        assert isinstance(score, int)
        
        # Test with negative placement (should handle gracefully)
        placement = {"row": -1, "col": 0, "direction": "horizontal"}
        score = self.generator._calculate_placement_score(grid, "TEST", placement, placed_words)
        assert isinstance(score, int)

async def test_full_generation_stress():
    """Stress test full generation to catch index errors."""
    generator = CrosswordGeneratorFixed(vector_service=None)
    
    # Mock word selection to return test words
    test_words = [
        {"word": "CAT", "clue": "Feline pet"},
        {"word": "DOG", "clue": "Man's best friend"}, 
        {"word": "BIRD", "clue": "Flying animal"},
        {"word": "FISH", "clue": "Aquatic animal"},
        {"word": "ELEPHANT", "clue": "Large mammal"},
        {"word": "TIGER", "clue": "Striped cat"},
        {"word": "HORSE", "clue": "Riding animal"},
        {"word": "BEAR", "clue": "Large carnivore"},
        {"word": "WOLF", "clue": "Pack animal"},
        {"word": "LION", "clue": "King of jungle"}
    ]
    
    generator._select_words = lambda topics, difficulty, use_ai: test_words
    
    # Run multiple generation attempts
    for i in range(20):
        try:
            result = await generator.generate_puzzle(["animals"], "medium", use_ai=False)
            if result:
                print(f"✅ Generation {i+1} succeeded")
            else:
                print(f"⚠️ Generation {i+1} returned None")
        except IndexError as e:
            print(f"❌ Index error in generation {i+1}: {e}")
            raise
        except Exception as e:
            print(f"⚠️ Other error in generation {i+1}: {e}")
            # Don't raise for other errors, just continue
    
    print("✅ All stress test generations completed without index errors!")

if __name__ == "__main__":
    # Run tests
    print("🧪 Running comprehensive bounds checking tests...")
    
    # Run pytest on this file
    import subprocess
    result = subprocess.run([sys.executable, "-m", "pytest", __file__, "-v"], 
                          capture_output=True, text=True)
    
    print("STDOUT:", result.stdout)
    if result.stderr:
        print("STDERR:", result.stderr)
    
    # Run stress test
    print("\n🏋️ Running stress test...")
    asyncio.run(test_full_generation_stress())