158 lines
5.2 KiB
Python
Executable File
158 lines
5.2 KiB
Python
Executable File
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def test_reference_bundle_running_task_remains_running_after_elapsed_time(tmp_path, monkeypatch):
|
|
from apps.web.vue3 import main
|
|
|
|
monkeypatch.setattr(main, "WORKSPACE_ROOT", tmp_path / "workspace")
|
|
task = {
|
|
"id": "task_ref_long_running",
|
|
"task_id": "task_ref_long_running",
|
|
"stage": "reference_bundle_download",
|
|
"job_id": "job_ref_long_running",
|
|
"status": "running",
|
|
"message": "正在后台下载参考物种注释包",
|
|
"created_at": (datetime.now() - timedelta(seconds=500)).isoformat(),
|
|
"start_time": (datetime.now() - timedelta(seconds=500)).isoformat(),
|
|
"timeout_sec": 300,
|
|
"payload": {"job_id": "job_ref_long_running", "timeout_sec": 300},
|
|
}
|
|
|
|
main._apply_reference_bundle_stale_watchdog(task)
|
|
|
|
assert task["status"] == "running"
|
|
assert task["pending_seconds"] >= 500
|
|
assert task["progress"]["phase"] == "running"
|
|
assert task["progress"]["indeterminate"] is True
|
|
assert "action_required" not in task
|
|
assert "retryable" not in task
|
|
assert "result" not in task
|
|
|
|
status_path = (
|
|
tmp_path
|
|
/ "workspace"
|
|
/ "job_ref_long_running"
|
|
/ "final"
|
|
/ "reference_bundle_tasks"
|
|
/ "task_ref_long_running.json"
|
|
)
|
|
assert status_path.exists()
|
|
persisted = json.loads(status_path.read_text(encoding="utf-8"))
|
|
assert persisted["status"] == "running"
|
|
assert persisted["progress"]["phase"] == "running"
|
|
|
|
|
|
def test_reference_bundle_success_clears_stale_fallback_fields(tmp_path, monkeypatch):
|
|
from apps.web.vue3 import main
|
|
|
|
monkeypatch.setattr(main, "WORKSPACE_ROOT", tmp_path / "workspace")
|
|
monkeypatch.setattr(main, "INPUT_ROOT", tmp_path / "input")
|
|
task_id = "task_ref_success_cleanup"
|
|
main.tasks[task_id] = {
|
|
"id": task_id,
|
|
"task_id": task_id,
|
|
"stage": "reference_bundle_download",
|
|
"job_id": "job_ref_success_cleanup",
|
|
"status": "failed",
|
|
"message": "old timeout",
|
|
"action_required": True,
|
|
"retryable": True,
|
|
"recommended_action": "fallback_or_retry_reference_download",
|
|
"result": {"success": False, "can_fallback_ath": True},
|
|
}
|
|
|
|
def fake_download_reference_annotation_bundle(**_kwargs):
|
|
return {
|
|
"status": "success",
|
|
"species_code": "soe",
|
|
"files": {"pep": "/tmp/soe.pep.fasta", "cds": "/tmp/soe.cds.fasta"},
|
|
"missing_required": [],
|
|
}
|
|
|
|
monkeypatch.setattr(
|
|
"apps.web.pipeline_backend.download_reference_annotation_bundle",
|
|
fake_download_reference_annotation_bundle,
|
|
)
|
|
|
|
main.run_reference_bundle_download_task(
|
|
task_id,
|
|
{
|
|
"job_id": "job_ref_success_cleanup",
|
|
"species_code": "soe",
|
|
"species_name": "Spinacia oleracea",
|
|
"taxon_id": "3562",
|
|
"requested_stages": ["ppi"],
|
|
"include_recommended": False,
|
|
"accession": "",
|
|
"timeout_sec": 300,
|
|
},
|
|
)
|
|
|
|
task = main.tasks[task_id]
|
|
assert task["status"] == "success"
|
|
assert task["result"]["success"] is True
|
|
assert "action_required" not in task
|
|
assert "retryable" not in task
|
|
assert "recommended_action" not in task
|
|
|
|
|
|
|
|
def test_ppi_resolution_prefers_downloaded_near_reference_over_recent_at(tmp_path):
|
|
from apps.web import pipeline_backend as backend
|
|
|
|
input_dir = tmp_path / "input"
|
|
final_dir = tmp_path / "workspace" / "final"
|
|
logs_dir = tmp_path / "workspace" / "logs"
|
|
soe_dir = input_dir / "soe"
|
|
soe_dir.mkdir(parents=True)
|
|
final_dir.mkdir(parents=True)
|
|
logs_dir.mkdir(parents=True)
|
|
(soe_dir / "soe.pep.fasta").write_text(">SOE1\nMAAA\n", encoding="utf-8")
|
|
(soe_dir / "soe.reference_bundle_download.manifest.json").write_text(
|
|
json.dumps({
|
|
"stage": "reference_bundle_download",
|
|
"status": "success",
|
|
"species_code": "soe",
|
|
"files": {"pep": str(soe_dir / "soe.pep.fasta")},
|
|
}),
|
|
encoding="utf-8",
|
|
)
|
|
(final_dir / "Ft.SPL.ortholog.manifest.json").write_text(
|
|
json.dumps({
|
|
"stage": "ortholog",
|
|
"status": "success",
|
|
"prefix": "Ft.SPL",
|
|
"reference_species": ["At"],
|
|
"stage_context": {"reference_species": ["At"]},
|
|
}),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
job = backend.JobInfo(
|
|
job_id="job_ref_prefer_downloaded",
|
|
input_dir=str(input_dir),
|
|
workspace_dir=str(tmp_path / "workspace"),
|
|
logs_dir=str(logs_dir),
|
|
created_at="",
|
|
)
|
|
|
|
resolution = backend.resolve_ppi_species_resolution(
|
|
job=job,
|
|
prefix="Ft.SPL",
|
|
ppi_species="auto",
|
|
target_species=["Ft"],
|
|
reference_species=["At"],
|
|
family="SPL",
|
|
ignore_existing_ortholog=True,
|
|
requested_stages=["ppi"],
|
|
)
|
|
|
|
assert resolution["reference_species"] == ""
|
|
assert resolution["selected_string_species"] == ""
|
|
assert resolution["status"] == "confirmation_required"
|
|
assert resolution["candidates"][0]["selected_string_taxon_id"] == "3562"
|