111 lines
6.0 KiB
Python
111 lines
6.0 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
from apps.web import pipeline_backend
|
|
|
|
|
|
def _valid_manifest() -> dict:
|
|
return {
|
|
"stage": "wgs_qc", "status": "success", "prefix": "Cohort", "job_id": "qc-job",
|
|
"wgs_sample_set_id": "wgs-samples:" + "a" * 64,
|
|
"wgs_analysis_id": "wgs-analysis:" + "b" * 64,
|
|
"artifacts": {
|
|
"wgs_sample_config": {"role": "wgs_sample_config"},
|
|
**{f"{role}.S1": {"role": role, "sample_id": "S1"}
|
|
for role in ("clean_reads_r1", "clean_reads_r2", "fastp_html", "fastp_json")},
|
|
},
|
|
}
|
|
|
|
|
|
def test_wgs_manifest_validator_rejects_wrong_config_role_and_incomplete_sample_roles() -> None:
|
|
wrong_role = _valid_manifest()
|
|
wrong_role["artifacts"]["wgs_sample_config"]["role"] = "input_config"
|
|
assert not pipeline_backend._valid_wgs_qc_manifest(wrong_role, job_id="qc-job", prefix="Cohort", sample_ids={"S1"})
|
|
incomplete = _valid_manifest()
|
|
del incomplete["artifacts"]["fastp_json.S1"]
|
|
assert not pipeline_backend._valid_wgs_qc_manifest(incomplete, job_id="qc-job", prefix="Cohort", sample_ids={"S1"})
|
|
|
|
|
|
def test_wgs_qc_backend_maps_api_config_to_canonical_cli_and_requires_fresh_manifest(monkeypatch, tmp_path: Path) -> None:
|
|
input_dir = tmp_path / "input"
|
|
workspace = tmp_path / "workspace"
|
|
input_dir.mkdir()
|
|
(input_dir / "S1.R1.fastq").write_text("@r\nA\n+\nI\n", encoding="utf-8")
|
|
(input_dir / "S1.R2.fastq").write_text("@r\nA\n+\nI\n", encoding="utf-8")
|
|
config = input_dir / "wgs_samples.yaml"
|
|
config.write_text("samples:\n S1:\n r1: S1.R1.fastq\n r2: S1.R2.fastq\n", encoding="utf-8")
|
|
final_dir = workspace / "final"
|
|
final_dir.mkdir(parents=True)
|
|
manifest_path = final_dir / "Cohort.wgs_qc.manifest.json"
|
|
manifest_path.write_text(json.dumps({"stage": "wgs_qc", "status": "success", "prefix": "Cohort", "artifacts": {}}), encoding="utf-8")
|
|
captured = {}
|
|
|
|
def fake_run(cmd, log_file, cwd):
|
|
captured["cmd"] = list(cmd)
|
|
manifest_path.write_text(json.dumps({
|
|
"stage": "wgs_qc", "status": "success", "prefix": "Cohort",
|
|
"artifacts": {"wgs_sample_config": {"role": "wgs_sample_config", "path": "final/config.yaml"}, **{f"{role}.S1": {"role": role, "sample_id": "S1"} for role in ("clean_reads_r1", "clean_reads_r2", "fastp_html", "fastp_json")}},
|
|
"job_id": "qc-job",
|
|
"wgs_sample_set_id": "wgs-samples:" + "a" * 64,
|
|
"wgs_analysis_id": "wgs-analysis:" + "a" * 64,
|
|
}), encoding="utf-8")
|
|
return 0
|
|
|
|
monkeypatch.setattr(pipeline_backend, "run_cmd_logged", fake_run)
|
|
message, returned_manifest = pipeline_backend.run_stage(
|
|
stage="wgs_qc",
|
|
job=pipeline_backend.JobInfo("qc-job", str(input_dir), str(workspace), str(workspace / "logs"), "now"),
|
|
prefix="Cohort",
|
|
wgs_sample_config=str(config),
|
|
wgs_fastp="/opt/fastp",
|
|
wgs_threads=3,
|
|
wgs_max_jobs=2,
|
|
)
|
|
|
|
assert "completed" in message.lower()
|
|
assert returned_manifest == manifest_path
|
|
assert captured["cmd"][:3] == [pipeline_backend.sys.executable, "-m", "apps.cli.run_wgs_qc"]
|
|
assert captured["cmd"][captured["cmd"].index("--wgs-samples") + 1] == str(config)
|
|
assert captured["cmd"][captured["cmd"].index("--fastp") + 1] == "/opt/fastp"
|
|
|
|
|
|
def test_wgs_qc_backend_rejects_missing_or_multiple_sample_configs(tmp_path: Path) -> None:
|
|
job = pipeline_backend.JobInfo("qc-job", str(tmp_path / "input"), str(tmp_path / "workspace"), str(tmp_path / "logs"), "now")
|
|
message, _ = pipeline_backend.run_stage(stage="wgs_qc", job=job, prefix="Cohort")
|
|
assert "wgs_samples.yaml" in message
|
|
input_dir = Path(job.input_dir); workspace = Path(job.workspace_dir)
|
|
input_dir.mkdir(); workspace.mkdir()
|
|
(input_dir / "wgs_samples.yaml").write_text("samples: {}\n", encoding="utf-8")
|
|
(workspace / "wgs_samples.yaml").write_text("samples: {}\n", encoding="utf-8")
|
|
message, _ = pipeline_backend.run_stage(stage="wgs_qc", job=job, prefix="Cohort")
|
|
assert "exactly one" in message
|
|
|
|
|
|
def test_wgs_qc_backend_rejects_fresh_but_identityless_manifest(monkeypatch, tmp_path: Path) -> None:
|
|
input_dir = tmp_path / "input"; workspace = tmp_path / "workspace"; input_dir.mkdir()
|
|
for mate in ("R1", "R2"):
|
|
(input_dir / f"S1.{mate}.fastq").write_text("@r\nA\n+\nI\n", encoding="utf-8")
|
|
config = input_dir / "wgs_samples.yaml"
|
|
config.write_text("samples:\n S1:\n r1: S1.R1.fastq\n r2: S1.R2.fastq\n", encoding="utf-8")
|
|
manifest = workspace / "final" / "Cohort.wgs_qc.manifest.json"; manifest.parent.mkdir(parents=True)
|
|
def fake_run(*_args, **_kwargs):
|
|
manifest.write_text(json.dumps({"stage": "wgs_qc", "status": "success", "prefix": "Cohort", "job_id": "qc-job", "artifacts": {}}), encoding="utf-8")
|
|
return 0
|
|
monkeypatch.setattr(pipeline_backend, "run_cmd_logged", fake_run)
|
|
message, _ = pipeline_backend.run_stage(stage="wgs_qc", job=pipeline_backend.JobInfo("qc-job", str(input_dir), str(workspace), str(workspace / "logs"), "now"), prefix="Cohort", wgs_sample_config=str(config))
|
|
assert "failed" in message.lower()
|
|
|
|
|
|
def test_wgs_qc_backend_rejects_stale_success_manifest(monkeypatch, tmp_path: Path) -> None:
|
|
input_dir = tmp_path / "input"; workspace = tmp_path / "workspace"; input_dir.mkdir()
|
|
for mate in ("R1", "R2"):
|
|
(input_dir / f"S1.{mate}.fastq").write_text("@r\nA\n+\nI\n", encoding="utf-8")
|
|
config = input_dir / "wgs_samples.yaml"
|
|
config.write_text("samples:\n S1:\n r1: S1.R1.fastq\n r2: S1.R2.fastq\n", encoding="utf-8")
|
|
manifest = workspace / "final" / "Cohort.wgs_qc.manifest.json"; manifest.parent.mkdir(parents=True)
|
|
manifest.write_text(json.dumps(_valid_manifest()), encoding="utf-8")
|
|
monkeypatch.setattr(pipeline_backend, "run_cmd_logged", lambda *_args, **_kwargs: 0)
|
|
message, returned = pipeline_backend.run_stage(stage="wgs_qc", job=pipeline_backend.JobInfo("qc-job", str(input_dir), str(workspace), str(workspace / "logs"), "now"), prefix="Cohort", wgs_sample_config=str(config))
|
|
assert "failed" in message.lower()
|
|
assert returned == manifest
|