Files

107 lines
3.8 KiB
Python
Raw Permalink Normal View History

2026-08-03 18:42:47 +08:00
from pathlib import Path
import pytest
from gfa_core.extension_stage.api_converter import (
ApiConversionError,
_build_repair_prompt,
_validate_spec,
)
def test_validate_spec_preserves_input_roles_and_param_placeholders():
spec = _validate_spec({
"original_command": "seqtk subseq At.pep.fa At.id > out.fa",
"command_template": "seqtk subseq {input_fasta} {input_file} > {output_dir}/hits.fa --min {param_min_length}",
"input_roles": {
"input_fasta": {"role": "target_pep_fasta", "resolve_by": "config_species"},
"input_file": {"role": "target_id_list", "resolve_by": "species_final_gfmembers_id"},
},
"parameters": {
"min-length": {"default": 80, "editable": True},
"internal flag": {"default": "legacy", "editable": False},
},
})
assert spec["input_roles"]["input_fasta"]["role"] == "target_pep_fasta"
assert spec["input_roles"]["input_file"]["required"] is True
assert "min_length" in spec["parameters"]
assert spec["parameters"]["internal_flag"]["editable"] is False
assert spec["requires_user_confirmation"] is False
def test_validate_spec_rejects_editable_parameter_not_used_by_template():
with pytest.raises(ApiConversionError, match="editable parameter"):
_validate_spec({
"original_command": "blastp -evalue 1e-5",
"command_template": "blastp -query {input_fasta} -out {output_dir}/blast.tsv -evalue 1e-5",
"parameters": {"evalue": {"default": "1e-5", "editable": True}},
})
def test_repair_prompt_does_not_forbid_parameter_placeholders(tmp_path: Path):
raw_code = tmp_path / "code"
raw_data = tmp_path / "data"
raw_code.mkdir()
raw_data.mkdir()
(raw_code / "run.sh").write_text("echo test\n", encoding="utf-8")
(raw_data / "At.id").write_text("AT1G01010\n", encoding="utf-8")
prompt = _build_repair_prompt(
extension={
"template_type": "shell_pipeline",
"entrypoint": {"command_template": "echo {input_file} > {output_dir}/out.txt"},
"parameters": {},
"inputs": {},
"outputs": {},
"requirements": {},
"runtime": {},
},
raw_code=raw_code,
raw_data=raw_data,
validation_context={"status": "failed", "error": "missing value"},
attempt=1,
max_attempts=3,
lessons=[],
)
assert "For v1, do not put parameter placeholders" not in prompt
assert "Every parameter with editable=true must be used" in prompt
assert "input_roles" in prompt
def test_repair_prompt_includes_phase_specific_rules(tmp_path: Path):
raw_code = tmp_path / "code"
raw_data = tmp_path / "data"
raw_code.mkdir()
raw_data.mkdir()
(raw_code / "run.sh").write_text("echo test\n", encoding="utf-8")
(raw_data / "input.fa").write_text(">g1\nMA\n", encoding="utf-8")
prompt = _build_repair_prompt(
extension={
"template_type": "shell_pipeline",
"entrypoint": {"command_template": "bash {code_dir}/run.sh > {output_dir}/out.txt"},
"parameters": {},
"inputs": {},
"outputs": {},
"requirements": {},
"runtime": {},
},
raw_code=raw_code,
raw_data=raw_data,
validation_context={
"repair_phase": "publish_gate",
"status": "failed",
"error": "Publish gate failed",
"publish_checks": {"errors": ["golden original-vs-converted comparison did not pass"]},
},
attempt=1,
max_attempts=20,
lessons=[],
)
assert '"repair_phase": "publish_gate"' in prompt
assert "If repair_phase is publish_gate" in prompt
assert "writes outputs under work/current directory instead of {output_dir}" in prompt