from pathlib import Path import pytest import canned_prompts as cp @pytest.fixture() def package(tmp_path: Path) -> Path: pkg = tmp_path / "pkg" pkg.mkdir() (pkg / "prompt.yaml").write_text( """\ format: canned-prompt/v0.1 id: demo/hello name: Hello version: 1.0.0 summary: Say hello. template: prompt.md inputs: - name: person required: true parameters: tone: type: enum values: [warm, formal] default: warm """, encoding="utf-8", ) (pkg / "prompt.md").write_text( "Say hello to {{ person }} in a {{ tone }} tone.\n", encoding="utf-8" ) return pkg def test_validate_and_render(package: Path) -> None: manifest = cp.validate_package(package) values = cp.resolve_values(manifest, {"person": "Ada"}) rendered = cp.render_template((package / "prompt.md").read_text(), values) assert rendered == "Say hello to Ada in a warm tone.\n" def test_missing_required_input_fails(package: Path) -> None: manifest = cp.validate_package(package) with pytest.raises(cp.CannedPromptError, match="missing required input"): cp.resolve_values(manifest, {}) def test_undeclared_placeholder_fails(package: Path) -> None: (package / "prompt.md").write_text("{{ missing }}\n", encoding="utf-8") with pytest.raises(cp.CannedPromptError, match="undeclared placeholders"): cp.validate_package(package) def write_pkg(pkg: Path, manifest: str, template: str = "{{ greeting }}\n") -> Path: pkg.mkdir(exist_ok=True) (pkg / "prompt.yaml").write_text(manifest, encoding="utf-8") (pkg / "prompt.md").write_text(template, encoding="utf-8") return pkg BASE = """\ format: canned-prompt/v0.1 id: demo/defaults name: Defaults version: 1.0.0 summary: Exercise input defaults. template: prompt.md """ def test_static_default_fills_optional_input(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: false default: "hello there" """, ) manifest = cp.validate_package(pkg) resolution = cp.resolve_inputs(manifest, {}) assert resolution.values["greeting"] == "hello there" assert resolution.origins["greeting"] == "default" assert cp.render_template("{{ greeting }}", resolution.values) == "hello there" def test_supplied_value_overrides_static_default(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: false default: "hello there" """, ) manifest = cp.validate_package(pkg) resolution = cp.resolve_inputs(manifest, {"greeting": "hi"}) assert resolution.values["greeting"] == "hi" assert resolution.origins["greeting"] == "supplied" def test_derived_default_uses_static_fallback(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ dependencies: prompts: - id: context/greeting version: 1.0.0 requirement: generate inputs: - name: greeting required: false default: derive: context/greeting value: "(none)" """, ) manifest = cp.validate_package(pkg) resolution = cp.resolve_inputs(manifest, {}) assert resolution.values["greeting"] == "(none)" assert resolution.origins["greeting"] == "fallback (not derived)" assert resolution.underivable == [] def test_derived_default_without_fallback_is_underivable(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ dependencies: prompts: - id: context/greeting version: 1.0.0 requirement: generate inputs: - name: greeting required: false default: derive: context/greeting """, ) manifest = cp.validate_package(pkg) resolution = cp.resolve_inputs(manifest, {}) assert resolution.underivable == ["greeting"] assert "greeting" not in resolution.values with pytest.raises(cp.CannedPromptError, match="unresolved placeholder"): cp.render_template("{{ greeting }}", resolution.values) def test_inline_derive_is_valid(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: false default: derive: prompt: Produce a greeting suited to the audience. value: "(none)" """, ) manifest = cp.validate_package(pkg) assert cp.resolve_inputs(manifest, {}).values["greeting"] == "(none)" def test_default_with_required_true_fails(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: true default: "hello" """, ) with pytest.raises(cp.CannedPromptError, match="required: true"): cp.validate_package(pkg) def test_derive_reference_must_be_declared(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: false default: derive: context/greeting """, ) with pytest.raises(cp.CannedPromptError, match="not declared in dependencies"): cp.validate_package(pkg) def test_inline_derive_cannot_also_reference(tmp_path: Path) -> None: pkg = write_pkg( tmp_path / "p", BASE + """\ inputs: - name: greeting required: false default: derive: prompt: Produce a greeting. id: context/greeting """, ) with pytest.raises(cp.CannedPromptError, match="cannot also reference"): cp.validate_package(pkg)