Skip to content

Commit a52c416

Browse files
ameynertclaude
andauthored
test: add unit tests for Variant, ReferenceMapping, Haplotype, and _parse_pop_freqs (#27)
Extends test_remap_divref.py with direct tests for the model methods and private helper that were previously only exercised indirectly via reference_mapping(): - Variant.render(): SNP, insertion, deletion - Haplotype.parsed_variants(): single variant, multiple variants, caching (second call returns same list object) - Haplotype.contig(): returns chromosome of first variant - ReferenceMapping.variants_involved_str(): empty, single, multiple - _parse_pop_freqs(): all floats, mixed nulls, single null <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Tests * Expanded test coverage for variant parsing, formatting, and helper functions * Added tests for variant rendering formats, haplotype parsing with caching verification, population frequency parsing, and error handling for malformed input <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
1 parent 4b6a2fc commit a52c416

1 file changed

Lines changed: 152 additions & 1 deletion

File tree

divref/tests/tools/test_remap_divref.py

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
"""Tests for Haplotype.reference_mapping coordinate translation in remap_divref."""
1+
"""Tests for remap_divref models and helper functions."""
22

33
from typing import Any
44

5+
import pytest
6+
57
from divref.tools.remap_divref import Haplotype
68
from divref.tools.remap_divref import ReferenceMapping
9+
from divref.tools.remap_divref import Variant
10+
from divref.tools.remap_divref import _parse_pop_freqs
711

812

913
def create_haplotype(
@@ -156,3 +160,150 @@ def test_large_insertion_with_null_frequencies() -> None:
156160
"nfe": [0.0],
157161
"sas": [0.0],
158162
}
163+
164+
165+
# ---------------------------------------------------------------------------
166+
# Variant.render
167+
# ---------------------------------------------------------------------------
168+
169+
170+
def test_variant_render_snp() -> None:
171+
assert Variant(chromosome="chr1", position=100, reference="A", alternate="T").render() == (
172+
"chr1:100:A:T"
173+
)
174+
175+
176+
def test_variant_render_insertion() -> None:
177+
assert Variant(chromosome="chr2", position=200, reference="A", alternate="ATG").render() == (
178+
"chr2:200:A:ATG"
179+
)
180+
181+
182+
def test_variant_render_deletion() -> None:
183+
assert Variant(chromosome="chrX", position=300, reference="ATG", alternate="A").render() == (
184+
"chrX:300:ATG:A"
185+
)
186+
187+
188+
# ---------------------------------------------------------------------------
189+
# Haplotype.parsed_variants and .contig
190+
# ---------------------------------------------------------------------------
191+
192+
193+
def test_parsed_variants_single() -> None:
194+
hap = create_haplotype(variants="chr1:100:A:T", n_variants=1)
195+
vs = hap.parsed_variants()
196+
assert len(vs) == 1
197+
assert vs[0].chromosome == "chr1"
198+
assert vs[0].position == 100
199+
assert vs[0].reference == "A"
200+
assert vs[0].alternate == "T"
201+
202+
203+
def test_parsed_variants_multiple() -> None:
204+
hap = create_haplotype(variants="chr1:100:A:T,chr1:200:CC:G", n_variants=2)
205+
vs = hap.parsed_variants()
206+
assert len(vs) == 2
207+
assert vs[1].position == 200
208+
assert vs[1].reference == "CC"
209+
210+
211+
def test_parsed_variants_cached() -> None:
212+
# Second call returns the exact same list object (no re-parsing)
213+
hap = create_haplotype(variants="chr1:100:A:T,chr1:200:C:G", n_variants=2)
214+
assert hap.parsed_variants() is hap.parsed_variants()
215+
216+
217+
def test_contig_returns_first_variant_chromosome() -> None:
218+
hap = create_haplotype(variants="chr5:100:A:T,chr5:200:C:G", n_variants=2)
219+
assert hap.contig() == "chr5"
220+
221+
222+
# ---------------------------------------------------------------------------
223+
# ReferenceMapping.variants_involved_str
224+
# ---------------------------------------------------------------------------
225+
226+
227+
def test_variants_involved_str_empty() -> None:
228+
rm = ReferenceMapping(
229+
chromosome="chr1",
230+
start=100,
231+
end=200,
232+
variants_involved=[],
233+
first_variant_index=None,
234+
last_variant_index=None,
235+
population_frequencies={},
236+
)
237+
assert rm.variants_involved_str() == ""
238+
239+
240+
def test_variants_involved_str_single() -> None:
241+
rm = ReferenceMapping(
242+
chromosome="chr1",
243+
start=100,
244+
end=200,
245+
variants_involved=[Variant(chromosome="chr1", position=150, reference="A", alternate="T")],
246+
first_variant_index=0,
247+
last_variant_index=0,
248+
population_frequencies={},
249+
)
250+
assert rm.variants_involved_str() == "chr1:150:A:T"
251+
252+
253+
def test_variants_involved_str_multiple() -> None:
254+
rm = ReferenceMapping(
255+
chromosome="chr1",
256+
start=100,
257+
end=300,
258+
variants_involved=[
259+
Variant(chromosome="chr1", position=150, reference="A", alternate="T"),
260+
Variant(chromosome="chr1", position=200, reference="CC", alternate="C"),
261+
],
262+
first_variant_index=0,
263+
last_variant_index=1,
264+
population_frequencies={},
265+
)
266+
assert rm.variants_involved_str() == "chr1:150:A:T,chr1:200:CC:C"
267+
268+
269+
# ---------------------------------------------------------------------------
270+
# _parse_pop_freqs
271+
# ---------------------------------------------------------------------------
272+
273+
274+
def test_parse_pop_freqs_all_floats() -> None:
275+
assert _parse_pop_freqs("0.1,0.2,0.3") == [0.1, 0.2, 0.3]
276+
277+
278+
def test_parse_pop_freqs_nulls_become_zero() -> None:
279+
assert _parse_pop_freqs("0.1,null,0.3") == [0.1, 0.0, 0.3]
280+
281+
282+
def test_parse_pop_freqs_single_null() -> None:
283+
assert _parse_pop_freqs("null") == [0.0]
284+
285+
286+
# ---------------------------------------------------------------------------
287+
# Error paths: malformed variant strings
288+
# ---------------------------------------------------------------------------
289+
290+
291+
def test_parsed_variants_empty_string_raises() -> None:
292+
# variants="" yields one empty token which cannot be split into 4 fields.
293+
hap = create_haplotype(variants="", n_variants=0)
294+
with pytest.raises(ValueError):
295+
hap.parsed_variants()
296+
297+
298+
def test_parsed_variants_too_few_fields_raises() -> None:
299+
# "chr1:100:A" has only 3 colon-delimited fields; unpacking into 4 raises ValueError.
300+
hap = create_haplotype(variants="chr1:100:A", n_variants=1)
301+
with pytest.raises(ValueError):
302+
hap.parsed_variants()
303+
304+
305+
def test_contig_malformed_variants_raises() -> None:
306+
# contig() delegates to parsed_variants(), so a malformed string propagates the error.
307+
hap = create_haplotype(variants="", n_variants=0)
308+
with pytest.raises(ValueError):
309+
hap.contig()

0 commit comments

Comments
 (0)