@@ -127,5 +127,100 @@ def test_both_present_does_not_raise(self):
127127 self .assertIsNotNone (report .ancestor )
128128
129129
130+ class TestWritePathMissingSpouse (unittest .TestCase ):
131+ """Bug 12913: ``write_path`` raised ``HandleError: Handle is None``
132+ when a family in the descent chain had only one parent defined
133+ (so ``spouse_handle`` came back as ``None``) and the addon called
134+ ``get_person_from_handle(None)`` directly. The guard added in
135+ this PR catches the ``None`` before the lookup and falls through
136+ to the existing ``'N.N.'`` spouse-name fallback that was already
137+ in place for the "spouse object missing" case."""
138+
139+ @classmethod
140+ def setUpClass (cls ):
141+ try :
142+ cls .impl = _load_impl ()
143+ except ImportError as err :
144+ raise unittest .SkipTest ("Gramps not importable: %s" % err )
145+ cls .LinesOfDescendency = cls .impl .LinesOfDescendency
146+
147+ @staticmethod
148+ def _report_instance (database , doc ):
149+ """Build a ``LinesOfDescendency`` instance via ``__new__`` so
150+ ``Report.__init__`` (which we don't need) doesn't run, and
151+ wire up the minimum attributes ``write_path`` touches."""
152+ report = TestWritePathMissingSpouse .LinesOfDescendency .__new__ (
153+ TestWritePathMissingSpouse .LinesOfDescendency
154+ )
155+ report .database = database
156+ report .doc = doc
157+ report .line = 1
158+ return report
159+
160+ @staticmethod
161+ def _person (handle , gramps_id , parents_family_handle ):
162+ """Mock person with the minimal API ``write_path`` touches."""
163+ person = mock .MagicMock ()
164+ person .get_handle .return_value = handle
165+ person .get_gramps_id .return_value = gramps_id
166+ person .get_main_parents_family_handle .return_value = (
167+ parents_family_handle
168+ )
169+ return person
170+
171+ @staticmethod
172+ def _family (mother_handle , father_handle , relationship ):
173+ family = mock .MagicMock ()
174+ family .get_mother_handle .return_value = mother_handle
175+ family .get_father_handle .return_value = father_handle
176+ family .get_relationship .return_value = relationship
177+ return family
178+
179+ def test_single_parent_family_does_not_raise (self ):
180+ """The reporter's case: a family in the chain has only one
181+ parent defined. Pre-fix, ``get_person_from_handle(None)``
182+ raised ``HandleError: Handle is None`` and torpedoed the
183+ whole report."""
184+ # Chain: ancestor (handle "A") -> child (handle "C")
185+ # Family F0037 has father "A" and mother = None ("Reeves, Maria"
186+ # removed per reporter's repro). When traversing from ancestor
187+ # to child, spouse_handle resolves to None.
188+ ancestor = self ._person ("A" , "I0001" , parents_family_handle = None )
189+ child = self ._person ("C" , "I0055" , parents_family_handle = "F0037" )
190+ # Family with only the father, no mother:
191+ family = self ._family (
192+ mother_handle = None ,
193+ father_handle = "A" ,
194+ relationship = self .impl .FamilyRelType .MARRIED ,
195+ )
196+
197+ database = mock .MagicMock ()
198+ database .get_person_from_handle .side_effect = lambda h : {
199+ "A" : ancestor ,
200+ "C" : child ,
201+ }[h ]
202+ database .get_family_from_handle .return_value = family
203+
204+ doc = mock .MagicMock ()
205+ report = self ._report_instance (database , doc )
206+
207+ # Pre-fix this would raise HandleError; post-fix it must
208+ # complete and never call get_person_from_handle with None.
209+ report .write_path (["A" , "C" ])
210+
211+ # Verify get_person_from_handle was NEVER called with None
212+ # -- the whole point of the guard.
213+ for call in database .get_person_from_handle .call_args_list :
214+ self .assertIsNotNone (call .args [0 ])
215+
216+ # Verify the user-visible output still uses the existing
217+ # 'N.N.' fallback for the missing-spouse case.
218+ written_calls = [str (c ) for c in doc .write_text .call_args_list ]
219+ self .assertTrue (
220+ any ("N.N." in text for text in written_calls ),
221+ "Expected 'N.N.' fallback in rendered text; got %r" % written_calls ,
222+ )
223+
224+
130225if __name__ == "__main__" :
131226 unittest .main ()
0 commit comments