abc123 / crossword-app /backend-py /test-integration /test_bounds_comprehensive.py
vimalk78's picture
Add complete Python backend with AI-powered crossword generation
38c016b
raw
history blame
11.3 kB
#!/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())