import json
from pathlib import Path
import pytest
import ferro_hgvs
G_INPUT = "NC_000001.11:g.1003C>A"
G_FS_INPUT = "NC_000001.11:g.1003del"
TRANSCRIPT = "NM_TEST.1"
G_UTR_INPUT = "NC_000002.12:g.2001A>G"
TRANSCRIPT_LONG = "NM_LONG.1"
def _reference_json(tmp_path: Path) -> str:
fixture = {
"transcripts": [
{
"id": "NM_TEST.1",
"gene_symbol": "TESTGENE",
"strand": "+",
"sequence": "ATGCGCTAA",
"cds_start": 1,
"cds_end": 9,
"exons": [
{
"number": 1,
"start": 1,
"end": 9,
"genomic_start": 1000,
"genomic_end": 1008,
}
],
"chromosome": "chr1",
"genomic_start": 1000,
"genomic_end": 1008,
}
],
"genomic_sequences": {
"chr1": "N" * 999 + "ATGCGCTAA" + "N" * 100,
},
}
path = tmp_path / "transcripts.json"
path.write_text(json.dumps(fixture))
return str(path)
def _long_reference_json(tmp_path: Path) -> str:
cds_seq = "ATGCGCAAAGGGTTTTAA" full_seq = "AAA" + cds_seq + "CCC" fixture = {
"transcripts": [
{
"id": "NM_LONG.1",
"gene_symbol": "LONGGENE",
"strand": "+",
"sequence": full_seq,
"cds_start": 4,
"cds_end": 21,
"exons": [
{
"number": 1,
"start": 1,
"end": 24,
"genomic_start": 2000,
"genomic_end": 2023,
}
],
"chromosome": "chr2",
"genomic_start": 2000,
"genomic_end": 2023,
}
],
"genomic_sequences": {
"chr2": "N" * 1999 + full_seq + "N" * 100,
},
}
path = tmp_path / "long_transcripts.json"
path.write_text(json.dumps(fixture))
return str(path)
def _proj(
tmp_path: Path, hgvs_string: str = G_INPUT, **kwargs: object
) -> ferro_hgvs.VariantProjection:
projector = ferro_hgvs.VariantProjector(reference_json=_reference_json(tmp_path), **kwargs)
return projector.project(hgvs_string, TRANSCRIPT)
def test_default_missense_unchanged(tmp_path: Path) -> None:
p = _proj(tmp_path).p_name
assert p == "NM_TEST.1:p.(Arg2Ser)"
def test_default_is_three_letter_ter(tmp_path: Path) -> None:
p = _proj(tmp_path, G_FS_INPUT).p_name
assert p == "NM_TEST.1:p.(Arg2AlafsTer?)"
assert "Ter" in p
assert "*" not in p
def test_constructor_protein_stop_star_switches_stop_token(tmp_path: Path) -> None:
p = _proj(tmp_path, G_FS_INPUT, protein_stop="star").p_name
assert p == "NM_TEST.1:p.(Arg2Alafs*?)"
assert "*" in p
assert "Ter" not in p
assert "Arg" in p
def test_constructor_option_a_one_letter_star(tmp_path: Path) -> None:
p = _proj(tmp_path, G_FS_INPUT, protein_stop="star", amino_acid_code="one").p_name
assert p == "NM_TEST.1:p.(R2Afs*?)"
assert "*" in p
assert "Ter" not in p
assert "Arg" not in p and "Ala" not in p
def test_per_call_option_b_overrides_default(tmp_path: Path) -> None:
proj = _proj(tmp_path, G_FS_INPUT) default_p = proj.p_name
assert default_p == "NM_TEST.1:p.(Arg2AlafsTer?)"
styled = proj.p_name_styled(protein_stop="star", amino_acid_code="one")
assert styled == "NM_TEST.1:p.(R2Afs*?)"
assert styled != default_p
def test_option_b_none_falls_back_to_constructor(tmp_path: Path) -> None:
proj = _proj(tmp_path, G_FS_INPUT, protein_stop="star", amino_acid_code="one")
assert proj.p_name_styled() == proj.p_name == "NM_TEST.1:p.(R2Afs*?)"
def test_option_b_partial_override(tmp_path: Path) -> None:
proj = _proj(tmp_path, G_FS_INPUT, protein_stop="star", amino_acid_code="one")
assert proj.p_name == "NM_TEST.1:p.(R2Afs*?)"
styled = proj.p_name_styled(amino_acid_code="three")
assert styled == "NM_TEST.1:p.(Arg2Alafs*?)"
assert "Arg" in styled assert "*" in styled assert "Ter" not in styled
@pytest.mark.parametrize("value", ["nope", "Ter", "TER", "*", "", "THREE"])
def test_invalid_protein_stop_raises(value: str) -> None:
with pytest.raises(ValueError):
ferro_hgvs.VariantProjector(protein_stop=value)
@pytest.mark.parametrize("value", ["nope", "3", "1", "", "ONE"])
def test_invalid_amino_acid_code_raises(value: str) -> None:
with pytest.raises(ValueError):
ferro_hgvs.VariantProjector(amino_acid_code=value)
def test_p_name_styled_none_passthrough(tmp_path: Path) -> None:
projector = ferro_hgvs.VariantProjector(reference_json=_long_reference_json(tmp_path))
proj = projector.project(G_UTR_INPUT, TRANSCRIPT_LONG)
assert proj.is_utr is True
assert proj.p_name is None
assert proj.p_name_styled(protein_stop="star") is None