import io from PIL import Image, ImageDraw from logimage.infrastructure.image.pillow_converter import PillowImageConverter from logimage.domain.value_objects.grid import Grid from tests.helpers import count_blocks def _make_jpeg_bytes(width: int = 100, height: int = 100, color: tuple[int, int, int] = (128, 64, 192)) -> bytes: img = Image.new("RGB", (width, height), color=color) buf = io.BytesIO() img.save(buf, format="JPEG") return buf.getvalue() def _make_silhouette_bytes(width: int = 100, height: int = 100) -> bytes: """Image avec un carré noir centré sur fond blanc.""" img = Image.new("RGB", (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) margin = width // 4 draw.rectangle([margin, margin, width - margin, height - margin], fill=(0, 0, 0)) buf = io.BytesIO() img.save(buf, format="PNG") return buf.getvalue() def test_output_grid_has_correct_dimensions() -> None: converter = PillowImageConverter() grid = converter.to_grid(_make_jpeg_bytes(), width=10, height=10) assert grid.width == 10 assert grid.height == 10 def test_output_is_grid_instance() -> None: converter = PillowImageConverter() result = converter.to_grid(_make_jpeg_bytes(), width=15, height=15) assert isinstance(result, Grid) def test_output_cells_are_booleans() -> None: converter = PillowImageConverter() grid = converter.to_grid(_make_jpeg_bytes(), width=10, height=10) for row_idx in range(grid.height): for cell in grid.row(row_idx): assert isinstance(cell, bool) def test_black_image_produces_all_true_cells() -> None: img = Image.new("RGB", (100, 100), color=(0, 0, 0)) buf = io.BytesIO() img.save(buf, format="JPEG") converter = PillowImageConverter() grid = converter.to_grid(buf.getvalue(), width=10, height=10) total_true = sum(cell for row in range(grid.height) for cell in grid.row(row)) assert total_true > 0 def test_white_image_produces_mostly_false_cells() -> None: img = Image.new("RGB", (100, 100), color=(255, 255, 255)) buf = io.BytesIO() img.save(buf, format="JPEG") converter = PillowImageConverter() grid = converter.to_grid(buf.getvalue(), width=10, height=10) total_true = sum(cell for row in range(grid.height) for cell in grid.row(row)) assert total_true < grid.width * grid.height def test_max_clue_blocks_respected_on_silhouette() -> None: """Une silhouette simple doit produire ≤ max_clue_blocks blocs par ligne/colonne.""" converter = PillowImageConverter(max_clue_blocks=6) grid = converter.to_grid(_make_silhouette_bytes(), width=20, height=20) for i in range(grid.height): blocks = count_blocks(grid.row(i)) assert blocks <= 6, f"Ligne {i} a {blocks} blocs (max=6)" for j in range(grid.width): blocks = count_blocks(grid.col(j)) assert blocks <= 6, f"Colonne {j} a {blocks} blocs (max=6)" def test_custom_max_clue_blocks() -> None: """max_clue_blocks=3 produit une grille encore plus simple.""" converter = PillowImageConverter(max_clue_blocks=3) grid = converter.to_grid(_make_silhouette_bytes(), width=15, height=15) for i in range(grid.height): assert count_blocks(grid.row(i)) <= 3 for j in range(grid.width): assert count_blocks(grid.col(j)) <= 3 def test_accepts_png_bytes() -> None: converter = PillowImageConverter() grid = converter.to_grid(_make_silhouette_bytes(), width=10, height=10) assert grid.width == 10