"""Integration tests for bulkgen.config.""" from __future__ import annotations from pathlib import Path import pytest import yaml from bulkgen.config import ( Defaults, TargetConfig, TargetType, infer_target_type, load_config, resolve_model, ) class TestLoadConfig: """Test loading and validating YAML config files end-to-end.""" def test_minimal_config(self, project_dir: Path) -> None: config_path = project_dir / "test.bulkgen.yaml" _ = config_path.write_text( yaml.dump({"targets": {"out.txt": {"prompt": "hello"}}}) ) config = load_config(config_path) assert "out.txt" in config.targets assert config.targets["out.txt"].prompt == "hello" assert config.defaults.text_model == "mistral-large-latest" assert config.defaults.image_model == "flux-pro-1.1" def test_full_config_with_all_fields(self, project_dir: Path) -> None: raw = { "defaults": { "text_model": "custom-text", "image_model": "custom-image", }, "targets": { "banner.png": { "prompt": "A wide banner", "model": "flux-dev", "width": 1920, "height": 480, "inputs": ["ref.png"], "reference_image": "ref.png", "control_images": ["ctrl.png"], }, "story.md": { "prompt": "Write a story", "inputs": ["banner.png"], }, }, } config_path = project_dir / "full.bulkgen.yaml" _ = config_path.write_text(yaml.dump(raw, default_flow_style=False)) config = load_config(config_path) assert config.defaults.text_model == "custom-text" assert config.defaults.image_model == "custom-image" banner = config.targets["banner.png"] assert banner.model == "flux-dev" assert banner.width == 1920 assert banner.height == 480 assert banner.reference_image == "ref.png" assert banner.control_images == ["ctrl.png"] story = config.targets["story.md"] assert story.model is None assert story.inputs == ["banner.png"] def test_empty_targets_rejected(self, project_dir: Path) -> None: config_path = project_dir / "empty.bulkgen.yaml" _ = config_path.write_text(yaml.dump({"targets": {}})) with pytest.raises(Exception, match="At least one target"): _ = load_config(config_path) def test_missing_prompt_rejected(self, project_dir: Path) -> None: config_path = project_dir / "bad.bulkgen.yaml" _ = config_path.write_text(yaml.dump({"targets": {"out.txt": {}}})) with pytest.raises(Exception): _ = load_config(config_path) class TestInferTargetType: """Test target type inference from file extensions.""" @pytest.mark.parametrize( "name", ["photo.png", "photo.jpg", "photo.jpeg", "photo.webp"] ) def test_image_extensions(self, name: str) -> None: assert infer_target_type(name) is TargetType.IMAGE @pytest.mark.parametrize("name", ["PHOTO.PNG", "PHOTO.JPG"]) def test_case_insensitive(self, name: str) -> None: assert infer_target_type(name) is TargetType.IMAGE @pytest.mark.parametrize("name", ["doc.md", "doc.txt"]) def test_text_extensions(self, name: str) -> None: assert infer_target_type(name) is TargetType.TEXT def test_unsupported_extension_raises(self) -> None: with pytest.raises(ValueError, match="unsupported extension"): _ = infer_target_type("data.csv") def test_no_extension_raises(self) -> None: with pytest.raises(ValueError, match="unsupported extension"): _ = infer_target_type("Makefile") class TestResolveModel: """Test model resolution (explicit vs. default).""" def test_explicit_model_wins(self) -> None: target = TargetConfig(prompt="x", model="my-model") assert resolve_model("out.txt", target, Defaults()) == "my-model" def test_default_text_model(self) -> None: target = TargetConfig(prompt="x") defaults = Defaults(text_model="custom-text") assert resolve_model("out.md", target, defaults) == "custom-text" def test_default_image_model(self) -> None: target = TargetConfig(prompt="x") defaults = Defaults(image_model="custom-image") assert resolve_model("out.png", target, defaults) == "custom-image"