from __future__ import annotations import inspect import json from pathlib import Path import runpy from apps.web.vue3.main import ExecuteRequest from gfa_core.mas.agents.planning_v2 import PlanningAgentV2 from gfa_core.mas.stage_capabilities import CAPABILITIES from gfa_core.mas.stage_catalog import EXECUTABLE_STAGES, STAGE_DEPENDENCIES from gfa_core.mas.tools.stage_tools import StageToolRegistry _CLI_FIXTURES = runpy.run_path( str(Path(__file__).with_name("test_wgs_variant_select_cli.py")) ) JOINT_ANALYSIS = _CLI_FIXTURES["JOINT_ANALYSIS"] REFERENCE = _CLI_FIXTURES["REFERENCE"] SAMPLES = _CLI_FIXTURES["SAMPLES"] _joint_authority = _CLI_FIXTURES["_joint_authority"] _tool = _CLI_FIXTURES["_tool"] def test_wgs_variant_select_vertical_registration_is_exact() -> None: assert STAGE_DEPENDENCIES["wgs_variant_select"] == [ "wgs_joint_genotyping" ] assert "wgs_variant_select" in EXECUTABLE_STAGES capability = CAPABILITIES["wgs_variant_select"] assert {item.role for item in capability.consumes} == { "joint_vcf", "joint_vcf_index", } assert {item.role for item in capability.provides} == { "selected_snp_vcf", "selected_snp_vcf_index", "selected_indel_vcf", "selected_indel_vcf_index", "variant_select_report", } definition = next( item for item in StageToolRegistry.STAGE_DEFINITIONS if item["id"] == "wgs_variant_select" ) assert definition["script"] == "run_wgs_variant_select.py" assert definition["required_inputs"] == ["wgs_joint_manifest"] assert PlanningAgentV2.REQUIRED_INPUTS["wgs_variant_select"] == [] def test_api_and_worker_boundary_carry_method_and_both_tools() -> None: request = ExecuteRequest( stage="wgs_variant_select", wgsJointManifest="final/Cohort.wgs_joint_genotyping.manifest.json", wgsMethod="bcftools", wgsGatk="/opt/gatk", wgsBcftools="/opt/bcftools", ) assert request.wgsJointManifest.endswith( ".wgs_joint_genotyping.manifest.json" ) assert request.wgsMethod == "bcftools" assert request.wgsGatk == "/opt/gatk" assert request.wgsBcftools == "/opt/bcftools" parameters = inspect.signature( __import__("apps.web.pipeline_backend", fromlist=["run_stage"]).run_stage ).parameters assert "wgs_joint_manifest" in parameters assert "wgs_method" in parameters assert "wgs_bcftools" in parameters def _backend_kwargs( workspace: Path, joint: Path, gatk: Path, bcftools: Path, method: str, ) -> dict: from apps.web import pipeline_backend return { "stage": "wgs_variant_select", "job": pipeline_backend.JobInfo( "job-1", "", str(workspace), str(workspace / "logs"), "now" ), "prefix": "Cohort", "wgs_joint_manifest": str(joint), "wgs_sample_set_id": SAMPLES, "wgs_reference_id": REFERENCE, "wgs_joint_analysis_id": JOINT_ANALYSIS, "wgs_method": method, "wgs_gatk": str(gatk), "wgs_bcftools": str(bcftools), } def test_backend_builds_exact_method_command_and_accepts_fresh_manifest( tmp_path: Path, monkeypatch ) -> None: from apps.cli.run_wgs_variant_select import main from apps.web import pipeline_backend workspace = tmp_path / "workspace" joint = _joint_authority(workspace) gatk = _tool(tmp_path, "gatk") bcftools = _tool(tmp_path, "bcftools") calls: list[list[str]] = [] def fake_run(command, *_args, **_kwargs): calls.append(command) return main(command[3:]) monkeypatch.setattr(pipeline_backend, "run_cmd_logged", fake_run) for method in ("gatk", "bcftools"): message, output = pipeline_backend.run_stage( **_backend_kwargs( workspace, joint, gatk, bcftools, method ) ) assert "completed" in message.lower() assert output == ( workspace / "final" / f"Cohort.wgs_variant_select.{method}.manifest.json" ) manifest = json.loads(output.read_text()) assert manifest["method"] == method assert manifest["parameters"]["method"] == method assert set(manifest["software"]) == {method} command = calls[-1] assert command[:3] == [ pipeline_backend.sys.executable, "-m", "apps.cli.run_wgs_variant_select", ] assert command[command.index("--joint-manifest") + 1] == str(joint) assert command[command.index("--method") + 1] == method def test_only_runtime_gatk_failure_returns_real_reason_and_fallback_metadata( tmp_path: Path, monkeypatch ) -> None: from apps.cli.run_wgs_variant_select import main from apps.web import pipeline_backend workspace = tmp_path / "workspace" joint = _joint_authority(workspace) gatk = _tool(tmp_path, "gatk") bcftools = _tool(tmp_path, "bcftools") monkeypatch.setenv("FAIL_POINT", "gatk:select:SNP") monkeypatch.setattr( pipeline_backend, "run_cmd_logged", lambda command, *_args, **_kwargs: main(command[3:]), ) message, failure_path = pipeline_backend.run_stage( **_backend_kwargs( workspace, joint, gatk, bcftools, "gatk" ) ) failure = json.loads(failure_path.read_text()) assert "SelectVariants SNP failed with exit code 23" in message assert "SelectVariants SNP failed with exit code 23" in failure["errors"][0] assert failure["fallback_method"] == "bcftools" assert failure["method"] == "gatk" def test_bcftools_failure_and_upstream_preflight_are_not_fallback_eligible( tmp_path: Path, monkeypatch ) -> None: from apps.cli.run_wgs_variant_select import main from apps.web import pipeline_backend workspace = tmp_path / "workspace" joint = _joint_authority(workspace) gatk = _tool(tmp_path, "gatk") bcftools = _tool(tmp_path, "bcftools") monkeypatch.setenv("FAIL_POINT", "bcftools:select:SNP") monkeypatch.setattr( pipeline_backend, "run_cmd_logged", lambda command, *_args, **_kwargs: main(command[3:]), ) _, failure_path = pipeline_backend.run_stage( **_backend_kwargs( workspace, joint, gatk, bcftools, "bcftools" ) ) failure = json.loads(failure_path.read_text()) assert "fallback_method" not in failure payload = json.loads(joint.read_text()) payload.pop("wgs_analysis_id") joint.write_text(json.dumps(payload)) message, output = pipeline_backend.run_stage( **_backend_kwargs( workspace, joint, gatk, bcftools, "gatk" ) ) assert output is None assert "fallback" not in message.lower() def test_worker_failure_payload_surfaces_only_manifest_approved_fallback() -> None: source = inspect.getsource( __import__( "gfa_core.mas.tools.stage_tools", fromlist=["StageToolExecutor"], ).StageToolExecutor.execute ) assert 'payload["fallback_method"] = manifest["fallback_method"]' in source