Skip to content

Commit 6b41868

Browse files
committed
Tests for reducers
1 parent c0021c2 commit 6b41868

12 files changed

+672
-0
lines changed

tests/unit/reducers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
from pyard.reducers import (
6+
Reducer,
7+
GGroupReducer,
8+
PGroupReducer,
9+
LGReducer,
10+
LGXReducer,
11+
WReducer,
12+
ExonReducer,
13+
U2Reducer,
14+
SReducer,
15+
DefaultReducer,
16+
StrategyFactory,
17+
)
18+
19+
20+
@pytest.fixture
21+
def mock_ard():
22+
"""Create comprehensive mock ARD instance"""
23+
ard = Mock()
24+
ard.ars_mappings = Mock()
25+
ard.ars_mappings.g_group = {"A*01:01": "A*01:01:01G"}
26+
ard.ars_mappings.p_group = {"A*01:01": "A*01:01P"}
27+
ard.ars_mappings.lgx_group = {"A*01:01:01": "A*01:01"}
28+
ard.ars_mappings.exon_group = {"A*01:01:01": "A*01:01:01"}
29+
ard.ars_mappings.dup_g = {}
30+
ard.code_mappings = Mock()
31+
ard.code_mappings.who_group = {}
32+
ard.db_connection = Mock()
33+
ard._config = {"ARS_as_lg": False}
34+
ard._is_allele_in_db = Mock(return_value=True)
35+
ard._is_who_allele = Mock(return_value=False)
36+
ard._redux_allele = Mock()
37+
ard.redux = Mock()
38+
ard.is_shortnull = Mock(return_value=False)
39+
ard.smart_sort_comparator = Mock()
40+
return ard
41+
42+
43+
def test_all_reducers_inherit_from_base(mock_ard):
44+
"""Test that all reducer classes inherit from Reducer base class"""
45+
reducers = [
46+
GGroupReducer(mock_ard),
47+
PGroupReducer(mock_ard),
48+
LGReducer(mock_ard),
49+
LGXReducer(mock_ard),
50+
WReducer(mock_ard),
51+
ExonReducer(mock_ard),
52+
U2Reducer(mock_ard),
53+
SReducer(mock_ard),
54+
DefaultReducer(mock_ard),
55+
]
56+
57+
for reducer in reducers:
58+
assert isinstance(reducer, Reducer)
59+
assert hasattr(reducer, "reduce")
60+
assert hasattr(reducer, "ard")
61+
62+
63+
def test_strategy_factory_creates_all_reducers(mock_ard):
64+
"""Test that StrategyFactory can create all reducer types"""
65+
factory = StrategyFactory(mock_ard)
66+
67+
strategies = ["G", "P", "lg", "lgx", "W", "exon", "U2", "S", "default"]
68+
69+
for strategy_type in strategies:
70+
strategy = factory.get_strategy(strategy_type)
71+
assert isinstance(strategy, Reducer)
72+
assert strategy.ard == mock_ard
73+
74+
75+
def test_all_reducers_have_ard_instance(mock_ard):
76+
"""Test that all reducers store ARD instance correctly"""
77+
reducers = [
78+
GGroupReducer(mock_ard),
79+
PGroupReducer(mock_ard),
80+
LGReducer(mock_ard),
81+
LGXReducer(mock_ard),
82+
WReducer(mock_ard),
83+
ExonReducer(mock_ard),
84+
U2Reducer(mock_ard),
85+
SReducer(mock_ard),
86+
DefaultReducer(mock_ard),
87+
]
88+
89+
for reducer in reducers:
90+
assert reducer.ard == mock_ard
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
from pyard.reducers.base_reducer import Reducer
6+
7+
8+
class ConcreteReducer(Reducer):
9+
"""Concrete implementation for testing abstract base class"""
10+
11+
def reduce(self, allele: str) -> str:
12+
return f"reduced_{allele}"
13+
14+
15+
def test_reducer_initialization():
16+
"""Test that Reducer can be initialized with ARD instance"""
17+
mock_ard = Mock()
18+
reducer = ConcreteReducer(mock_ard)
19+
assert reducer.ard == mock_ard
20+
21+
22+
def test_reducer_abstract_method():
23+
"""Test that reduce method is implemented in concrete class"""
24+
mock_ard = Mock()
25+
reducer = ConcreteReducer(mock_ard)
26+
result = reducer.reduce("A*01:01")
27+
assert result == "reduced_A*01:01"
28+
29+
30+
def test_reducer_cannot_be_instantiated():
31+
"""Test that abstract Reducer class cannot be instantiated directly"""
32+
mock_ard = Mock()
33+
with pytest.raises(TypeError):
34+
Reducer(mock_ard)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
from pyard.reducers.default_reducer import DefaultReducer
6+
from pyard.exceptions import InvalidAlleleError
7+
8+
9+
@pytest.fixture
10+
def mock_ard():
11+
"""Create mock ARD instance"""
12+
ard = Mock()
13+
ard.ars_mappings = Mock()
14+
ard.ars_mappings.p_group = Mock()
15+
ard.ars_mappings.g_group = Mock()
16+
ard.is_valid_allele = Mock()
17+
return ard
18+
19+
20+
def test_reduce_p_group_allele(mock_ard):
21+
"""Test reduction of P group allele"""
22+
mock_ard.ars_mappings.p_group.values.return_value = ["A*01:01P"]
23+
24+
reducer = DefaultReducer(mock_ard)
25+
result = reducer.reduce("A*01:01P")
26+
27+
assert result == "A*01:01P"
28+
29+
30+
def test_reduce_g_group_allele(mock_ard):
31+
"""Test reduction of G group allele"""
32+
mock_ard.ars_mappings.g_group.values.return_value = ["A*01:01G"]
33+
34+
reducer = DefaultReducer(mock_ard)
35+
result = reducer.reduce("A*01:01G")
36+
37+
assert result == "A*01:01G"
38+
39+
40+
def test_reduce_valid_allele_in_db(mock_ard):
41+
"""Test reduction of valid allele in database"""
42+
mock_ard.is_valid_allele.return_value = True
43+
44+
reducer = DefaultReducer(mock_ard)
45+
result = reducer.reduce("A*01:01")
46+
47+
assert result == "A*01:01"
48+
mock_ard.is_valid_allele.assert_called_once_with("A*01:01")
49+
50+
51+
def test_reduce_invalid_allele_raises_error(mock_ard):
52+
"""Test that invalid allele raises InvalidAlleleError"""
53+
mock_ard.ars_mappings.p_group.values.return_value = []
54+
mock_ard.ars_mappings.g_group.values.return_value = []
55+
mock_ard.is_valid_allele.return_value = False
56+
57+
reducer = DefaultReducer(mock_ard)
58+
59+
with pytest.raises(InvalidAlleleError, match="INVALID is an invalid allele"):
60+
reducer.reduce("INVALID")
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock, patch
5+
from pyard.reducers.exon_reducer import ExonReducer
6+
7+
8+
@pytest.fixture
9+
def mock_ard():
10+
"""Create mock ARD instance"""
11+
ard = Mock()
12+
ard.ars_mappings = Mock()
13+
ard.ars_mappings.exon_group = {"A*01:01:01": "A*01:01:01"}
14+
ard.is_shortnull = Mock()
15+
ard.redux = Mock()
16+
return ard
17+
18+
19+
def test_reduce_allele_in_exon_group(mock_ard):
20+
"""Test reduction of allele in exon group mapping"""
21+
reducer = ExonReducer(mock_ard)
22+
result = reducer.reduce("A*01:01:01")
23+
24+
assert result == "A*01:01:01"
25+
26+
27+
def test_reduce_allele_with_expression_char_shortnull(mock_ard):
28+
"""Test reduction of allele with expression character that is shortnull"""
29+
mock_ard.ars_mappings.exon_group = {"A*01:01:01N": "A*01:01:01"}
30+
mock_ard.is_shortnull.return_value = True
31+
32+
with patch("pyard.reducers.exon_reducer.expression_chars", "N"):
33+
reducer = ExonReducer(mock_ard)
34+
result = reducer.reduce("A*01:01:01N")
35+
36+
assert result == "A*01:01:01N"
37+
mock_ard.is_shortnull.assert_called_once_with("A*01:01:01N")
38+
39+
40+
def test_reduce_allele_with_expression_char_not_shortnull(mock_ard):
41+
"""Test reduction of allele with expression character that is not shortnull"""
42+
mock_ard.ars_mappings.exon_group = {"A*01:01:01Q": "A*01:01:01"}
43+
mock_ard.is_shortnull.return_value = False
44+
45+
with patch("pyard.reducers.exon_reducer.expression_chars", "Q"):
46+
reducer = ExonReducer(mock_ard)
47+
result = reducer.reduce("A*01:01:01Q")
48+
49+
assert result == "A*01:01:01"
50+
51+
52+
def test_reduce_allele_not_in_exon_group_w_redux_same(mock_ard):
53+
"""Test reduction when W redux returns same allele"""
54+
mock_ard.redux.return_value = "B*07:02"
55+
56+
reducer = ExonReducer(mock_ard)
57+
result = reducer.reduce("B*07:02")
58+
59+
assert result == "B*07:02"
60+
mock_ard.redux.assert_called_once_with("B*07:02", "W")
61+
62+
63+
def test_reduce_allele_not_in_exon_group_w_redux_two_field(mock_ard):
64+
"""Test reduction when W redux returns 2-field allele"""
65+
mock_ard.redux.return_value = "B*07:02"
66+
67+
reducer = ExonReducer(mock_ard)
68+
result = reducer.reduce("B*07:02:01")
69+
70+
assert result == "B*07:02:01"
71+
72+
73+
def test_reduce_allele_not_in_exon_group_recursive(mock_ard):
74+
"""Test recursive reduction when W redux returns different allele"""
75+
mock_ard.redux.side_effect = ["B*07:02:01:01", "B*07:02:01"]
76+
77+
reducer = ExonReducer(mock_ard)
78+
result = reducer.reduce("B*07:02:XX")
79+
80+
assert result == "B*07:02:01"
81+
assert mock_ard.redux.call_count == 2
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
from pyard.reducers.g_reducer import GGroupReducer
6+
7+
8+
@pytest.fixture
9+
def mock_ard():
10+
"""Create mock ARD instance"""
11+
ard = Mock()
12+
ard.ars_mappings = Mock()
13+
ard.ars_mappings.g_group = {"A*01:01": "A*01:01:01G", "A*02:01": "A*02:01:02G"}
14+
ard.ars_mappings.dup_g = {"A*02:01": "A*02:01:01G/A*02:01:02G"}
15+
ard.ars_mappings.p_group = Mock()
16+
ard.is_valid_allele = Mock(return_value=True)
17+
return ard
18+
19+
20+
def test_reduce_allele_in_g_group(mock_ard):
21+
"""Test reduction of allele in G group mapping"""
22+
reducer = GGroupReducer(mock_ard)
23+
result = reducer.reduce("A*01:01")
24+
25+
assert result == "A*01:01:01G"
26+
27+
28+
def test_reduce_allele_in_dup_g(mock_ard):
29+
"""Test reduction of allele in duplicate G group mapping"""
30+
reducer = GGroupReducer(mock_ard)
31+
result = reducer.reduce("A*02:01")
32+
33+
assert result == "A*02:01:01G/A*02:01:02G"
34+
35+
36+
def test_reduce_allele_not_in_g_group_calls_super(mock_ard):
37+
"""Test that allele not in G group calls parent reduce method"""
38+
reducer = GGroupReducer(mock_ard)
39+
result = reducer.reduce("B*07:02")
40+
41+
assert result == "B*07:02"
42+
mock_ard.is_valid_allele.assert_called_once_with("B*07:02")
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
from pyard.reducers.lg_reducer import LGXReducer, LGReducer
6+
7+
8+
@pytest.fixture
9+
def mock_ard():
10+
"""Create mock ARD instance"""
11+
ard = Mock()
12+
ard.ars_mappings = Mock()
13+
ard.ars_mappings.lgx_group = {"A*01:01:01": "A*01:01"}
14+
ard._config = {"ARS_as_lg": False}
15+
return ard
16+
17+
18+
class TestLGXReducer:
19+
def test_reduce_allele_in_lgx_group(self, mock_ard):
20+
"""Test reduction of allele in LGX group mapping"""
21+
reducer = LGXReducer(mock_ard)
22+
result = reducer.reduce("A*01:01:01")
23+
24+
assert result == "A*01:01"
25+
26+
def test_reduce_allele_not_in_lgx_group(self, mock_ard):
27+
"""Test reduction of allele not in LGX group returns first 2 fields"""
28+
reducer = LGXReducer(mock_ard)
29+
result = reducer.reduce("B*07:02:01:03")
30+
31+
assert result == "B*07:02"
32+
33+
def test_reduce_two_field_allele(self, mock_ard):
34+
"""Test reduction of already 2-field allele"""
35+
reducer = LGXReducer(mock_ard)
36+
result = reducer.reduce("C*01:02")
37+
38+
assert result == "C*01:02"
39+
40+
41+
class TestLGReducer:
42+
def test_reduce_single_allele_with_g_suffix(self, mock_ard):
43+
"""Test reduction adds 'g' suffix to single allele"""
44+
reducer = LGReducer(mock_ard)
45+
result = reducer.reduce("A*01:01:01")
46+
47+
assert result == "A*01:01g"
48+
49+
def test_reduce_single_allele_with_ars_suffix(self, mock_ard):
50+
"""Test reduction adds 'ARS' suffix when configured"""
51+
mock_ard._config = {"ARS_as_lg": True}
52+
reducer = LGReducer(mock_ard)
53+
result = reducer.reduce("A*01:01:01")
54+
55+
assert result == "A*01:01ARS"
56+
57+
def test_reduce_multiple_alleles_with_g_suffix(self, mock_ard):
58+
"""Test reduction adds 'g' suffix to multiple alleles"""
59+
mock_ard.ars_mappings.lgx_group = {"A*01:01:01": "A*01:01/A*01:02"}
60+
reducer = LGReducer(mock_ard)
61+
result = reducer.reduce("A*01:01:01")
62+
63+
assert result == "A*01:01g/A*01:02g"

0 commit comments

Comments
 (0)