112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from gfa_core.mas.tools.stage_identity import StageIdentity, get_stage_status_for_identity
|
|
|
|
|
|
def _trait_manifest(final: Path, *, phenotype_scope: str, digest12: str) -> Path:
|
|
trait = final / f"cohort.gwas_trait.{digest12}.trait.txt"
|
|
report = final / f"cohort.gwas_trait.{digest12}.report.md"
|
|
environment = final / f"cohort.gwas_trait.{digest12}.environment.txt"
|
|
for path in (trait, report, environment):
|
|
path.write_text(path.name + "\n", encoding="utf-8")
|
|
|
|
def artifact(path: Path, role: str) -> dict:
|
|
return {
|
|
"role": role, "path": f"final/{path.name}", "full_path": str(path.resolve()),
|
|
"type": "text/plain", "size": path.stat().st_size,
|
|
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
}
|
|
|
|
manifest = {
|
|
"manifest_version": "1.0", "stage": "gwas_trait", "prefix": "cohort",
|
|
"job_id": "job", "run_id": f"gwas_trait_{digest12}", "status": "success",
|
|
"phenotype_scope_id": phenotype_scope,
|
|
"analysis_scope_id": "gwas-analysis:" + digest12.ljust(64, "0"),
|
|
"elapsed_sec": 1.2,
|
|
"artifacts": {
|
|
"trait_table": artifact(trait, "trait_table"),
|
|
"report": artifact(report, "report"),
|
|
"environment": artifact(environment, "environment"),
|
|
},
|
|
}
|
|
path = final / f"cohort.gwas_trait.{digest12}.manifest.json"
|
|
path.write_text(json.dumps(manifest), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def test_status_and_results_select_exact_phenotype_scope(monkeypatch, tmp_path: Path) -> None:
|
|
from apps.web.vue3 import main as web
|
|
|
|
workspace_root = tmp_path / "workspace"
|
|
final = workspace_root / "job" / "final"
|
|
final.mkdir(parents=True)
|
|
wanted = "phenotype:" + "a" * 64
|
|
_trait_manifest(final, phenotype_scope=wanted, digest12="aaaaaaaaaaaa")
|
|
_trait_manifest(final, phenotype_scope="phenotype:" + "b" * 64, digest12="bbbbbbbbbbbb")
|
|
monkeypatch.setattr(web, "WORKSPACE_ROOT", workspace_root)
|
|
|
|
report = get_stage_status_for_identity(
|
|
job_id="job",
|
|
identity=StageIdentity(stage="gwas_trait", prefix="cohort", phenotype_scope_id=wanted),
|
|
workspace_root=workspace_root,
|
|
)
|
|
assert report.completed is True
|
|
assert report.manifest["phenotype_scope_id"] == wanted
|
|
|
|
payload = asyncio.run(web.get_results(
|
|
job_id="job", stage="gwas_trait", rnaseq_scope_id="", scope_id="",
|
|
genotype_scope_id="", phenotype_scope_id=wanted,
|
|
covariate_scope_id="", analysis_scope_id="",
|
|
))
|
|
assert payload["gwas_trait"]["status"] == "success"
|
|
assert {item["role"] for item in payload["gwas_trait"]["files"]} == {
|
|
"manifest", "trait_table", "report", "environment",
|
|
}
|
|
assert all("aaaaaaaaaaaa" in item["name"] for item in payload["gwas_trait"]["files"])
|
|
|
|
without_scope = asyncio.run(web.get_results(
|
|
job_id="job", stage="gwas_trait", rnaseq_scope_id="", scope_id="",
|
|
genotype_scope_id="", phenotype_scope_id="",
|
|
covariate_scope_id="", analysis_scope_id="",
|
|
))
|
|
assert without_scope["gwas_trait"]["files"] == []
|
|
|
|
|
|
def test_results_rehydration_does_not_replay_success_after_failed_attempt(
|
|
monkeypatch, tmp_path: Path,
|
|
) -> None:
|
|
from apps.web.vue3 import main as web
|
|
|
|
workspace_root = tmp_path / "workspace"
|
|
final = workspace_root / "job" / "final"
|
|
final.mkdir(parents=True)
|
|
success_path = _trait_manifest(
|
|
final, phenotype_scope="phenotype:" + "a" * 64, digest12="aaaaaaaaaaaa"
|
|
)
|
|
failed = json.loads(success_path.read_text(encoding="utf-8"))
|
|
failed.update({
|
|
"run_id": "gwas_trait_failed",
|
|
"status": "failed",
|
|
"start_time": "2026-07-24T00:00:00+00:00",
|
|
"end_time": "2026-07-24T00:00:01+00:00",
|
|
"artifacts": {},
|
|
"error": "forced failure",
|
|
})
|
|
(final / "cohort.gwas_trait.failed.manifest.json").write_text(
|
|
json.dumps(failed), encoding="utf-8"
|
|
)
|
|
monkeypatch.setattr(web, "WORKSPACE_ROOT", workspace_root)
|
|
|
|
payload = asyncio.run(web.get_results(
|
|
job_id="job", stage="gwas_trait", rnaseq_scope_id="", scope_id="",
|
|
genotype_scope_id="", phenotype_scope_id="",
|
|
covariate_scope_id="", analysis_scope_id="",
|
|
))
|
|
|
|
assert payload["gwas_trait"]["files"] == []
|