118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
"""tests/test_manifests.py —— manifest 解析/校验/跨阶段输入定位。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from app.services.bioflow.manifests import (
|
|
Manifest,
|
|
ManifestError,
|
|
artifact_paths,
|
|
input_resolution,
|
|
locate_manifest,
|
|
parse_manifest,
|
|
)
|
|
|
|
pytestmark = pytest.mark.bioflow_unit
|
|
|
|
|
|
def _write_manifest(workspace, stage, status="success", prefix="Ft.SPL", extra=None):
|
|
final = workspace / "final"
|
|
final.mkdir(parents=True, exist_ok=True)
|
|
raw = {
|
|
"manifest_version": "1", "job_id": "j1", "stage": stage, "status": status,
|
|
"prefix": prefix,
|
|
"artifacts": {
|
|
"final_members": {"artifact": "final_members",
|
|
"full_path": str(final / f"{prefix}.final_members.id"),
|
|
"type": "table", "size": 100},
|
|
},
|
|
"parameters": {"cpu": 8}, "metrics": {"final_members": 24},
|
|
}
|
|
raw.update(extra or {})
|
|
path = final / f"{prefix}.{stage}.manifest.json"
|
|
path.write_text(json.dumps(raw), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def test_parse_success(tmp_path):
|
|
_write_manifest(tmp_path, "identify")
|
|
m = parse_manifest(tmp_path, "identify")
|
|
assert isinstance(m, Manifest)
|
|
assert m.status == "success"
|
|
assert m.artifacts["final_members"]["type"] == "table"
|
|
assert m.metrics["final_members"] == 24
|
|
assert m.prefix == "Ft.SPL"
|
|
|
|
|
|
def test_missing_manifest(tmp_path):
|
|
with pytest.raises(ManifestError, match="缺失"):
|
|
parse_manifest(tmp_path, "identify")
|
|
|
|
|
|
def test_invalid_json(tmp_path):
|
|
final = tmp_path / "final"
|
|
final.mkdir(parents=True)
|
|
(final / "Ft.SPL.identify.manifest.json").write_text("{broken", encoding="utf-8")
|
|
with pytest.raises(ManifestError, match="非法 JSON"):
|
|
parse_manifest(tmp_path, "identify")
|
|
|
|
|
|
def test_missing_required_keys(tmp_path):
|
|
final = tmp_path / "final"
|
|
final.mkdir(parents=True)
|
|
(final / "Ft.SPL.identify.manifest.json").write_text('{"stage": "identify"}', encoding="utf-8")
|
|
with pytest.raises(ManifestError, match="缺必需字段"):
|
|
parse_manifest(tmp_path, "identify")
|
|
|
|
|
|
def test_invalid_status(tmp_path):
|
|
_write_manifest(tmp_path, "identify", status="weird")
|
|
with pytest.raises(ManifestError, match="status 非法"):
|
|
parse_manifest(tmp_path, "identify")
|
|
|
|
|
|
def test_failed_status_valid(tmp_path):
|
|
_write_manifest(tmp_path, "identify", status="failed")
|
|
m = parse_manifest(tmp_path, "identify")
|
|
assert m.status == "failed"
|
|
|
|
|
|
def test_locate_manifest_strict_no_fallback(tmp_path):
|
|
# 无 prefix 匹配时绝不兜底其他阶段 manifest(跨阶段误配会导致误判成败)
|
|
path = _write_manifest(tmp_path, "identify")
|
|
assert locate_manifest(tmp_path, "phylogeny", prefix="OTHER") is None
|
|
assert locate_manifest(tmp_path, "identify") == path
|
|
|
|
|
|
def test_artifact_paths():
|
|
artifacts = {
|
|
"a": {"full_path": "/x/a.id", "type": "table"},
|
|
"b": "/y/b.fa",
|
|
"c": {"path": "/z/c.txt"},
|
|
"d": {"relpath_or_hint": "final/d.txt"},
|
|
}
|
|
paths = artifact_paths(artifacts)
|
|
assert "/x/a.id" in paths
|
|
assert "/y/b.fa" in paths
|
|
assert "/z/c.txt" in paths
|
|
assert "final/d.txt" in paths
|
|
|
|
|
|
def test_input_resolution(tmp_path):
|
|
final = tmp_path / "final"
|
|
final.mkdir(parents=True, exist_ok=True)
|
|
(final / "Ft.SPL.final_members.id").write_text("gene1\ngene2\n", encoding="utf-8")
|
|
_write_manifest(tmp_path, "identify")
|
|
res = input_resolution(tmp_path, "identify")
|
|
assert res["resolved"] is True
|
|
assert res["manifest_path"]
|
|
assert res["inputs"]
|
|
|
|
|
|
def test_input_resolution_missing(tmp_path):
|
|
res = input_resolution(tmp_path, "identify")
|
|
assert res["resolved"] is False
|
|
assert "缺失" in res["error"]
|