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) MINIMAL = """\ format: canned-prompt/v0.1 id: practice/thing name: Thing version: 1.0.0 summary: A thing. template: prompt.md inputs: - name: greeting required: false default: hi """ def test_parse_reference() -> None: assert cp.parse_reference("practice/thing") == (None, "practice/thing") assert cp.parse_reference("house:practice/thing") == ("house", "practice/thing") with pytest.raises(cp.CannedPromptError, match="malformed reference"): cp.parse_reference("house:") def test_registry_name_falls_back_to_basename(tmp_path: Path) -> None: registry = tmp_path / "upstream" registry.mkdir() assert cp.registry_name(registry) == "upstream" def test_registry_name_from_manifest(tmp_path: Path) -> None: registry = tmp_path / "some-dir" registry.mkdir() (registry / "registry.yaml").write_text( "format: canned-prompt-registry/v0.1\nname: house\n", encoding="utf-8" ) assert cp.registry_name(registry) == "house" def test_registry_manifest_rejects_bad_format(tmp_path: Path) -> None: registry = tmp_path / "r" registry.mkdir() (registry / "registry.yaml").write_text( "format: something-else\nname: house\n", encoding="utf-8" ) with pytest.raises(cp.CannedPromptError, match="unsupported registry format"): cp.read_registry_manifest(registry) def test_registry_manifest_rejects_bad_policy(tmp_path: Path) -> None: registry = tmp_path / "r" registry.mkdir() (registry / "registry.yaml").write_text( "format: canned-prompt-registry/v0.1\n" "name: house\n" "namespaces:\n practice:\n policy: maybe\n", encoding="utf-8", ) with pytest.raises(cp.CannedPromptError, match="policy must be"): cp.read_registry_manifest(registry) def test_namespace_policy(tmp_path: Path) -> None: registry = tmp_path / "r" registry.mkdir() (registry / "registry.yaml").write_text( "format: canned-prompt-registry/v0.1\n" "name: house\n" "namespaces:\n practice:\n owner: Ada\n policy: closed\n", encoding="utf-8", ) assert cp.namespace_policy(registry, "practice/thing")[0] == "closed" assert cp.namespace_policy(registry, "scratch/thing")[0] == "open" def install_into(catalog: Path, registry: str) -> None: """Place a package in the catalog under a given registry name.""" dst = cp.catalog_package_path(catalog, registry, "practice/thing", "1.0.0") dst.mkdir(parents=True) (dst / "prompt.yaml").write_text(MINIMAL, encoding="utf-8") (dst / "prompt.md").write_text("{{ greeting }}\n", encoding="utf-8") def test_same_id_from_two_registries_coexists(tmp_path: Path) -> None: catalog = tmp_path / "catalog" install_into(catalog, "house") install_into(catalog, "upstream") assert cp.catalog_registries(catalog) == ["house", "upstream"] package_dir, registry = cp.resolve_installed(catalog, "house:practice/thing", None) assert registry == "house" assert package_dir.is_dir() def test_bare_id_in_two_registries_is_ambiguous(tmp_path: Path) -> None: catalog = tmp_path / "catalog" install_into(catalog, "house") install_into(catalog, "upstream") with pytest.raises(cp.CannedPromptError, match="more than one registry"): cp.resolve_installed(catalog, "practice/thing", None) def test_bare_id_in_one_registry_resolves(tmp_path: Path) -> None: catalog = tmp_path / "catalog" install_into(catalog, "house") _, registry = cp.resolve_installed(catalog, "practice/thing", None) assert registry == "house" def test_legacy_catalog_layout_is_reported(tmp_path: Path) -> None: catalog = tmp_path / "catalog" legacy = catalog / "practice" / "thing" / "1.0.0" legacy.mkdir(parents=True) (legacy / "prompt.yaml").write_text(MINIMAL, encoding="utf-8") (legacy / "prompt.md").write_text("{{ greeting }}\n", encoding="utf-8") with pytest.raises(cp.CannedPromptError, match="pre-registry-scoped"): cp.resolve_installed(catalog, "practice/thing", None)