1+ import unittest
2+ from unittest import mock
3+
4+ from gff3tool .lib .intra_model import intra_model
5+
6+
7+ class DummyGff :
8+ def __init__ (self , lines = None ):
9+ self .lines = lines or []
10+ self .line_errors = []
11+
12+ def add_line_error (self , line , error , log_level = None ):
13+ self .line_errors .append ((line , error , log_level ))
14+
15+ @staticmethod
16+ def overlap (a , b ):
17+ return not (a ["end" ] < b ["start" ] or b ["end" ] < a ["start" ])
18+
19+
20+ def _cds (line_index , start , end ):
21+ return {
22+ "line_type" : "feature" ,
23+ "line_index" : line_index ,
24+ "type" : "CDS" ,
25+ "start" : start ,
26+ "end" : end ,
27+ "attributes" : {"ID" : f"cds{ line_index } " },
28+ "children" : [],
29+ }
30+
31+
32+ class TestIntraModelEngine (unittest .TestCase ):
33+ def test_check_incomplete_flags_gene_without_mrna (self ):
34+ root = {
35+ "line_type" : "feature" ,
36+ "line_index" : 0 ,
37+ "type" : "gene" ,
38+ "attributes" : {"ID" : "gene1" },
39+ "children" : [{"type" : "exon" , "children" : []}],
40+ }
41+ gff = DummyGff ()
42+
43+ result = intra_model .check_incomplete (gff , root )
44+
45+ self .assertIsNotNone (result )
46+ self .assertEqual (result [0 ]["eCode" ], "Ema0004" )
47+ self .assertEqual (len (gff .line_errors ), 1 )
48+
49+ def test_check_merged_gene_parent_flags_non_overlapping_isoforms (self ):
50+ tx1 = {
51+ "line_index" : 1 ,
52+ "attributes" : {"ID" : "tx1" },
53+ "children" : [_cds (2 , 1 , 5 )],
54+ }
55+ tx2 = {
56+ "line_index" : 3 ,
57+ "attributes" : {"ID" : "tx2" },
58+ "children" : [_cds (4 , 20 , 30 )],
59+ }
60+ root = {
61+ "line_type" : "feature" ,
62+ "line_index" : 0 ,
63+ "type" : "gene" ,
64+ "attributes" : {"ID" : "gene1" },
65+ "children" : [tx1 , tx2 ],
66+ }
67+ gff = DummyGff ()
68+
69+ result = intra_model .check_merged_gene_parent (gff , root )
70+
71+ self .assertIsNotNone (result )
72+ self .assertEqual (result [0 ]["eCode" ], "Ema0009" )
73+ self .assertEqual (len (gff .line_errors ), 1 )
74+
75+ def test_main_noncanonical_skips_internal_stop_and_isoform_checks (self ):
76+ root = {
77+ "line_type" : "feature" ,
78+ "line_index" : 0 ,
79+ "type" : "gene" ,
80+ "start" : 1 ,
81+ "end" : 100 ,
82+ "attributes" : {"ID" : "gene1" },
83+ "children" : [],
84+ }
85+ gff = DummyGff (lines = [root ])
86+
87+ with mock .patch .object (intra_model .function4gff , "FIX_MISSING_ATTR" , autospec = True ), \
88+ mock .patch .object (intra_model , "check_internal_stop" , autospec = True ) as internal_stop , \
89+ mock .patch .object (intra_model , "check_distinct_isoform" , autospec = True ) as distinct_isoform , \
90+ mock .patch .object (intra_model , "check_merged_gene_parent" , autospec = True ) as merged_parent :
91+ intra_model .main (gff = gff , logger = mock .Mock (), noncanonical_gene = True )
92+
93+ internal_stop .assert_not_called ()
94+ distinct_isoform .assert_not_called ()
95+ merged_parent .assert_not_called ()
96+
97+ def test_main_canonical_collects_reported_errors (self ):
98+ root = {
99+ "line_type" : "feature" ,
100+ "line_index" : 0 ,
101+ "type" : "gene" ,
102+ "start" : 1 ,
103+ "end" : 100 ,
104+ "attributes" : {"ID" : "gene1" },
105+ "children" : [],
106+ }
107+ gff = DummyGff (lines = [root ])
108+
109+ with mock .patch .object (intra_model .function4gff , "FIX_MISSING_ATTR" , autospec = True ), \
110+ mock .patch .object (intra_model , "check_pseudo_child_type" , autospec = True , return_value = [{"eCode" : "Ema0005" }]), \
111+ mock .patch .object (intra_model , "check_redundant_length" , autospec = True , return_value = [{"eCode" : "Ema0001" }]), \
112+ mock .patch .object (intra_model , "check_incomplete" , autospec = True , return_value = [{"eCode" : "Ema0004" }]), \
113+ mock .patch .object (intra_model , "check_internal_stop" , autospec = True , return_value = [{"eCode" : "Ema0002" }]), \
114+ mock .patch .object (intra_model , "check_distinct_isoform" , autospec = True , return_value = [{"eCode" : "Ema0008" }]), \
115+ mock .patch .object (intra_model , "check_merged_gene_parent" , autospec = True , return_value = [{"eCode" : "Ema0009" }]):
116+ result = intra_model .main (gff = gff , logger = mock .Mock (), noncanonical_gene = False )
117+
118+ self .assertEqual ([r ["eCode" ] for r in result ], ["Ema0005" , "Ema0001" , "Ema0004" , "Ema0002" , "Ema0008" , "Ema0009" ])
119+
120+
121+ if __name__ == "__main__" :
122+ unittest .main ()
0 commit comments