#!/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())