File size: 3,277 Bytes
179e55d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""Figure Engine (Component 8) unit tests — no DB, no binary deps."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from app.figure.engine import (
    bar_chart_panel,
    domain_blocks_panel,
    esc,
    wrap_text,
)
from app.services.experiment_figures import build_experiment_figure

P53_CONTEXT = {
    "blast": {
        "count": 12,
        "top_hit": {"accession": "P04637", "description": "Cellular tumor antigen p53", "identity_pct": 100.0},
        "hits": [
            {"accession": "P04637", "description": "Cellular tumor antigen p53", "identity_pct": 100.0},
            {"accession": "Q9TTA1", "description": "Cellular tumor antigen p53 tupaia", "identity_pct": 99.7},
            {"accession": "P02340", "description": "Cellular tumor antigen p53 mouse", "identity_pct": 87.2},
        ],
    },
    "uniprot": {"accession": "P04637", "full_name": "Cellular tumor antigen p53", "gene_names": ["TP53"], "organism": "Homo sapiens"},
    "msa": {"sequence_count": 6, "method": "clustalo", "alignment_mode": "global"},
    "phylo": {"phylotree_newick": "(P04637:0.001,(P02340:0.02,Q9TTA1:0.03):0.005);"},
    "domains": {"sequence_length": 393, "domains": [
        {"accession": "IPR012345", "name": "p53_DNA_binding", "start": 92, "end": 292},
        {"accession": "IPR045493", "name": "p53_tetramerization", "start": 323, "end": 356},
    ]},
    "interpret": {"interpretation": "The query is homo sapiens p53. Strong homology to P04637."},
}


def test_esc_escapes_xml():
    assert "&lt;" in esc("<danger> & confusion")


def test_wrap_text_merges_short_phrases():
    lines = wrap_text("a b c", 5)
    assert lines and "a b c" in lines


def test_bar_chart_renders_bars_and_labels():
    svg = bar_chart_panel([("A", 100.0), ("B", 50.0)], x=0, y=0, w=300, h=120)
    assert svg.count("<rect") == 2
    assert "A" in svg and "B" in svg


def test_bar_chart_empty_placeholder():
    svg = bar_chart_panel([], x=0, y=0, w=300, h=120)
    assert "No data" in svg


def test_domain_blocks_within_axis():
    svg = domain_blocks_panel(
        [{"start": 92, "end": 292, "name": "p53_DNA_binding"}],
        seq_len=393, x=0, y=0, w=400, h=100,
    )
    assert "<line" in svg
    assert "393 aa" in svg


def test_domain_blocks_empty():
    assert "No domain data" in domain_blocks_panel([], seq_len=0, x=0, y=0, w=400, h=100)


def test_build_experiment_figure_full_context():
    svg = build_experiment_figure(P53_CONTEXT, "exp-1")
    assert svg.startswith("<svg")
    assert svg.rstrip().endswith("</svg>")
    assert svg.count("<svg") == svg.count("</svg>")
    for letter in "ABCDEF":
        assert letter in svg
    assert "BioNexus experiment analysis" in svg
    assert "Generated by BioNexus Figure Engine" in svg
    assert "P04637" in svg
    assert "InterPro" in svg.lower() or "interpro" in svg.lower()


def test_build_experiment_figure_empty_context_still_valid():
    svg = build_experiment_figure(None, "exp-empty")
    assert svg.count("<svg") == svg.count("</svg>")
    assert "No data" in svg or "No domain data" in svg


def test_build_experiment_figure_panel_count():
    svg = build_experiment_figure(P53_CONTEXT)
    assert svg.count("<circle") >= 6  # one label badge per panel