hugin/server/tests/test_translators.py
2026-10-27 08:33:21 +01:00

149 lines
7.6 KiB
Python

import pytest
import yaml
from conftest import example
from hugin_core.translators import BlockRow, File, Json, Config, Tool, translate
def frontmatter(data: bytes) -> tuple[dict, str, str]:
text = data.decode()
_, header, body = text.split("---\n", 2)
return yaml.safe_load(header), header.splitlines()[0], body
def test_skill_one_file_for_both():
package = example("pub-munin-nutzen/1.4.0")
claude = translate(package, Tool.CLAUDE, Config()).operations
opencode = translate(package, Tool.OPENCODE_1, Config()).operations
assert claude == opencode
assert [op.path for op in claude] == [
".claude/skills/pub-munin-nutzen/SKILL.md",
".claude/skills/pub-munin-nutzen/referenz/beispiele.md",
]
fields, header, body = frontmatter(claude[0].content)
assert header == "# Verwaltet von Hugin (pub-munin-nutzen 1.4.0). Änderungen über hugin.intern."
assert fields == {"name": "pub-munin-nutzen", "description": package.manifest.description_text,
"license": "intern"}
assert body.startswith("\n# Munin nutzen")
def test_skill_with_additions():
package = example("pub-munin-nutzen/1.4.0")
manifest = package.manifest.model_copy(update={"extra": {
"claude": {"allowed-tools": ["mcp__munin__recall"], "paths": ["**/*.go"]},
"opencode": {"metadata": {"opencode/slash": True}},
}})
package = type(package)(manifest, package.files)
fields, _, _ = frontmatter(translate(package, Tool.CLAUDE, Config()).operations[0].content)
assert fields["metadata"] == {"opencode/slash": "True"}
assert fields["allowed-tools"] == ["mcp__munin__recall"]
def test_skill_without_claude_compatibility():
package = example("pub-munin-nutzen/1.4.0")
ops = translate(package, Tool.OPENCODE_1, Config(opencode_reads_claude=False)).operations
assert ops[0].path == ".opencode/skills/pub-munin-nutzen/SKILL.md"
def test_rules():
package = example("pub-hausregeln/1.0.0")
claude = translate(package, Tool.CLAUDE, Config()).operations
assert claude[0].path == ".hugin/rules/pub-hausregeln.md"
assert claude[0].content.startswith(b"<!-- Verwaltet von Hugin (pub-hausregeln 1.0.0)")
assert claude[1] == BlockRow("pub-hausregeln", "CLAUDE.md", "hugin", "@.hugin/rules/pub-hausregeln.md")
opencode = translate(package, Tool.OPENCODE_1, Config()).operations
assert opencode[0] == claude[0]
assert opencode[1] == Json(None, "opencode.json", ("instructions",), ".hugin/rules/*.md", "list_item")
def test_local_mcp():
package = example("pub-zeit-lokal/1.0.0")
command = ["hugin", "mcp", "run", "pub-zeit-lokal"]
assert translate(package, Tool.OPENCODE_1, Config()).operations == [
Json("pub-zeit-lokal", "opencode.json", ("mcp", "zeit"), {
"type": "local", "command": command, "enabled": True,
"environment": {"ZEIT_ZONE_HINWEIS": "{env:ZEIT_ZONE_HINWEIS}"}})]
claude = translate(package, Tool.CLAUDE, Config())
assert claude.operations == [Json("pub-zeit-lokal", ".mcp.json", ("mcpServers", "zeit"), {
"type": "stdio", "command": "hugin", "args": command[1:], "env": {"ZEIT_ZONE_HINWEIS": "${ZEIT_ZONE_HINWEIS}"}})]
assert [h.as_dict() for h in claude.hints] == [
{"kind": "env_variable", "name": "ZEIT_ZONE_HINWEIS", "package": "pub-zeit-lokal"}]
def test_plugin():
package = example("pub-specht-plugin/1.0.0")
ops = translate(package, Tool.OPENCODE_1, Config()).operations
assert [o.path for o in ops] == [".opencode/plugins/pub-specht-plugin.ts",
".opencode/plugins/pub-specht-plugin/index.ts",
".opencode/plugins/pub-specht-plugin/lib/marke.ts"]
entry = ops[0].content.decode()
assert entry.startswith("// Verwaltet von Hugin (pub-specht-plugin 1.0.0)")
assert 'export * from "./pub-specht-plugin/index.ts"' in entry
assert ops[1].content == package.files["index.ts"]
def test_mcp():
package = example("pub-munin-anbindung/1.0.0")
claude = translate(package, Tool.CLAUDE, Config())
assert claude.operations == [Json("pub-munin-anbindung", ".mcp.json", ("mcpServers", "munin"), {
"type": "http", "url": "https://munin.intern/mcp",
"headers": {"Authorization": "Bearer ${MUNIN_KEY}"},
})]
assert [h.as_dict() for h in claude.hints] == [
{"kind": "env_variable", "name": "MUNIN_KEY", "package": "pub-munin-anbindung"},
]
opencode = translate(package, Tool.OPENCODE_1, Config()).operations
assert opencode == [Json("pub-munin-anbindung", "opencode.json", ("mcp", "munin"), {
"type": "remote", "url": "https://munin.intern/mcp", "enabled": True,
"headers": {"Authorization": "Bearer {env:MUNIN_KEY}"},
})]
def test_files_are_utf8_without_bom_with_lf():
for name in ("pub-munin-nutzen/1.4.0", "pub-hausregeln/1.0.0"):
for op in translate(example(name), Tool.CLAUDE, Config()).operations:
if isinstance(op, File):
assert not op.content.startswith(b"\xef\xbb\xbf") and b"\r\n" not in op.content
def test_command_arguments_per_tool():
package = example("pub-release-notiz/1.0.0")
claude = translate(package, Tool.CLAUDE, Config()).operations
fields, _, body = frontmatter(claude[0].content)
assert claude[0].path == ".claude/commands/pub-release-notiz.md"
assert fields["arguments"] == ["version", "zielgruppe"]
assert "Version $version, gedacht für $zielgruppe" in body and "$ARGUMENTS" in body
opencode = translate(package, Tool.OPENCODE_1, Config()).operations
fields, _, body = frontmatter(opencode[0].content)
assert opencode[0].path == ".opencode/commands/pub-release-notiz.md"
assert "arguments" not in fields
assert "Version $1, gedacht für $2" in body # OpenCode zählt ab 1
@pytest.mark.parametrize("capability, tools, permissions", [
(None, "Read, Grep, Glob", {"edit": "deny", "bash": "deny", "webfetch": "deny", "websearch": "deny"}),
(["write", "shell"], "Read, Grep, Glob, Edit, Write, Bash",
{"edit": "allow", "bash": "ask", "webfetch": "deny", "websearch": "deny"}),
("web", "Read, Grep, Glob, WebFetch, WebSearch",
{"edit": "deny", "bash": "deny", "webfetch": "allow", "websearch": "allow"}),
])
def test_subagent_capabilities(capability, tools, permissions):
package = example("pub-code-review/1.0.0")
package = type(package)(package.manifest.model_copy(update={"capability": capability}), package.files)
claude, _, _ = frontmatter(translate(package, Tool.CLAUDE, Config()).operations[0].content)
opencode, _, _ = frontmatter(translate(package, Tool.OPENCODE_1, Config()).operations[0].content)
assert claude["tools"] == tools
assert opencode["permission"] == permissions
def test_subagent_model_classes():
package = example("pub-code-review/1.0.0")
config = Config(models={"claude": {"thorough": "claude-opus-6"}, "opencode": {"thorough": "x/y"}})
claude, _, _ = frontmatter(translate(package, Tool.CLAUDE, config).operations[0].content)
opencode, _, _ = frontmatter(translate(package, Tool.OPENCODE_1, config).operations[0].content)
assert (claude["model"], opencode["model"]) == ("claude-opus-6", "x/y")
assert (claude["effort"], opencode["temperature"]) == ("high", 0.1) # Zusätze je Werkzeug
inherit = type(package)(package.manifest.model_copy(update={"model": "inherit"}), package.files)
claude, _, _ = frontmatter(translate(inherit, Tool.CLAUDE, config).operations[0].content)
opencode, _, _ = frontmatter(translate(inherit, Tool.OPENCODE_1, config).operations[0].content)
assert claude["model"] == "inherit" and "model" not in opencode