bio-nexus-api / tests /test_audit_msa_randomized.py
Samad14's picture
Upload folder using huggingface_hub (part 2)
e3a2796 verified
Raw History Blame Contribute Delete
6.31 kB
"""Independent deterministic audit checks for BioNexus MSA resilience paths."""
from __future__ import annotations
import random
from io import StringIO
from types import SimpleNamespace
import pytest
from Bio import Phylo
from app.tools import mafft_local
from app.tools.msa_fallback import (
_aligner,
_pairwise,
_upgma_newick,
progressive_msa,
)
SEED = 20260916
def _parse_alignment(fasta: str) -> dict[str, str]:
records: dict[str, str] = {}
current = None
for raw in fasta.splitlines():
line = raw.strip()
if not line:
continue
if line.startswith(">"):
current = line[1:].split()[0]
assert current not in records
records[current] = ""
else:
assert current is not None
records[current] += line.upper()
return records
def test_pairwise_fallback_preserves_residues_and_alignment_length():
g1, g2 = _pairwise(_aligner("dna"), "ACGT", "ACGTT")
assert len(g1) == len(g2)
assert g1.replace("-", "") == "ACGT"
assert g2.replace("-", "") == "ACGTT"
assert "-" in g1
def test_upgma_branch_lengths_are_ultrametric_for_known_matrix():
labels = ["A", "B", "C"]
dist = [
[0.0, 0.2, 0.8],
[0.2, 0.0, 0.8],
[0.8, 0.8, 0.0],
]
newick = _upgma_newick(labels, dist)
tree = Phylo.read(StringIO(newick), "newick")
leaves = {leaf.name: leaf for leaf in tree.get_terminals()}
root_distances = {name: tree.distance(tree.root, leaf) for name, leaf in leaves.items()}
assert set(leaves) == set(labels)
assert root_distances["A"] == pytest.approx(0.4, abs=1e-6)
assert root_distances["B"] == pytest.approx(0.4, abs=1e-6)
assert root_distances["C"] == pytest.approx(0.4, abs=1e-6)
def test_upgma_rejects_invalid_distance_contracts():
with pytest.raises(ValueError, match="dimensions"):
_upgma_newick(["A", "B"], [[0.0]])
with pytest.raises(ValueError, match="symmetric"):
_upgma_newick(["A", "B"], [[0.0, 0.1], [0.2, 0.0]])
with pytest.raises(ValueError, match="non-negative"):
_upgma_newick(["A", "B"], [[0.0, -0.1], [-0.1, 0.0]])
def test_progressive_fallback_rejects_duplicate_and_unsafe_ids():
with pytest.raises(ValueError, match="Duplicate"):
progressive_msa([("a", "ACGT"), ("a", "ACGA")], "dna")
with pytest.raises(ValueError, match="identifier"):
progressive_msa([("a,1", "ACGT"), ("b", "ACGA")], "dna")
def test_seeded_progressive_fallback_preserves_every_sequence_and_taxon():
rng = random.Random(SEED)
for replicate in range(20):
source: list[tuple[str, str]] = []
for index in range(rng.randint(2, 6)):
length = rng.randint(8, 24)
sequence = "".join(rng.choice("ACGT") for _ in range(length))
source.append((f"r{replicate}_s{index}", sequence))
aligned_fasta, newick = progressive_msa(source, "dna")
aligned = _parse_alignment(aligned_fasta)
assert set(aligned) == {sid for sid, _ in source}
assert len({len(seq) for seq in aligned.values()}) == 1
for sid, original in source:
assert aligned[sid].replace("-", "") == original
tree = Phylo.read(StringIO(newick), "newick")
leaves = [leaf.name for leaf in tree.get_terminals()]
assert sorted(leaves) == sorted(sid for sid, _ in source)
assert len(leaves) == len(set(leaves))
def test_mafft_strategy_flags_match_canonical_v7_definitions():
assert mafft_local.STRATEGY_FLAGS["fft-ns-2"] == ["--retree", "2", "--maxiterate", "0"]
assert mafft_local.STRATEGY_FLAGS["fft-ns-i"] == ["--maxiterate", "1000"]
assert mafft_local.STRATEGY_FLAGS["l-ins-i"] == ["--localpair", "--maxiterate", "1000"]
assert mafft_local.STRATEGY_FLAGS["g-ins-i"] == ["--globalpair", "--maxiterate", "1000"]
assert mafft_local.STRATEGY_FLAGS["e-ins-i"] == ["--genafpair", "--maxiterate", "1000"]
def test_local_mafft_command_and_output_integrity(monkeypatch):
fasta = ">a\nACGT\n>b\nACGTT\n"
alignment = ">a\nACG-T\n>b\nACGTT\n"
calls: list[list[str]] = []
monkeypatch.setattr(mafft_local, "_ensure_mafft", lambda: "/fake/mafft")
monkeypatch.setattr(mafft_local, "_MAFFT_VERSION", None)
def fake_run(cmd, **kwargs):
calls.append(list(cmd))
if "--version" in cmd:
return SimpleNamespace(returncode=0, stdout="v7.526 (2024/Apr/26)\n", stderr="")
return SimpleNamespace(returncode=0, stdout=alignment, stderr="")
monkeypatch.setattr(mafft_local.subprocess, "run", fake_run)
result = mafft_local.run_local_mafft(fasta, strategy="e-ins-i", threads=2, timeout=30)
assert result is not None
assert result["aln_fasta"] == alignment
assert result["strategy"] == "e-ins-i"
assert result["threads"] == 2
assert result["tool_version"] == "7.526"
execution = next(call for call in calls if "--version" not in call)
assert execution[0] == "/fake/mafft"
assert "--genafpair" in execution
assert "--maxiterate" in execution and "1000" in execution
assert "--thread" in execution and "2" in execution
def test_local_mafft_rejects_malformed_or_residue_changing_output(monkeypatch):
fasta = ">a\nACGT\n>b\nACGTT\n"
monkeypatch.setattr(mafft_local, "_ensure_mafft", lambda: "/fake/mafft")
outputs = iter([
">a\nACGT\n", # missing b
">a\nACG-T\n>b\nACGTA\n", # b residue changed
])
def fake_run(cmd, **kwargs):
if "--version" in cmd:
return SimpleNamespace(returncode=0, stdout="v7.526\n", stderr="")
return SimpleNamespace(returncode=0, stdout=next(outputs), stderr="")
monkeypatch.setattr(mafft_local.subprocess, "run", fake_run)
assert mafft_local.run_local_mafft(fasta) is None
assert mafft_local.run_local_mafft(fasta) is None
def test_local_mafft_rejects_unknown_strategy_before_execution(monkeypatch):
monkeypatch.setattr(mafft_local, "_ensure_mafft", lambda: "/fake/mafft")
monkeypatch.setattr(
mafft_local.subprocess,
"run",
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("MAFFT should not execute")),
)
assert mafft_local.run_local_mafft(">a\nACGT\n>b\nACGA\n", strategy="invented") is None