self.living = data["living"]
for x in data["names"]:
alt = not x.get("preferred", False)
+ name_kind = None
+ target_set = None
+
if x["type"] == "http://gedcomx.org/Nickname":
- self.nicknames.add(Name(x, self.tree, self.fid, "nickname", alt))
+ name_kind = "nickname"
+ target_set = self.nicknames
elif x["type"] == "http://gedcomx.org/BirthName":
- self.birthnames.add(Name(x, self.tree, self.fid, "birthname", alt))
+ name_kind = "birthname"
+ target_set = self.birthnames
elif x["type"] == "http://gedcomx.org/AlsoKnownAs":
- self.aka.add(Name(x, self.tree, self.fid, "aka", alt))
+ name_kind = "aka"
+ target_set = self.aka
elif x["type"] == "http://gedcomx.org/MarriedName":
- self.married.add(Name(x, self.tree, self.fid, "married", alt))
+ name_kind = "married"
+ target_set = self.married
else:
print("Unknown name type: " + x.get("type"), file=sys.stderr)
raise ValueError("Unknown name type")
+
+ name_obj = Name(x, self.tree, self.fid, name_kind, alt)
+ target_set.add(name_obj)
+
+ if not alt and not self.name:
+ self.name = name_obj
if "gender" in data:
if data["gender"]["type"] == "http://gedcomx.org/Male":
self.gender = "M"
sources = self.tree.fs.get_url(
"/platform/tree/persons/%s/sources" % self.fid
)
- if sources:
+ if sources and "sourceDescriptions" in sources and "persons" in sources:
for quote in sources["persons"][0]["sources"]:
source_id = quote["descriptionId"]
source_data = next(
sources = self.tree.fs.get_url(
"/platform/tree/couple-relationships/%s/sources" % self.fid
)
- for source in sources["sourceDescriptions"]:
- if (
- source["id"] in new_sources
- and source["id"] not in self.tree.sources
- ):
- self.tree.sources[source["id"]] = Source(
- source, self.tree
- )
+ if sources and "sourceDescriptions" in sources:
+ for source in sources["sourceDescriptions"]:
+ if (
+ source["id"] in new_sources
+ and source["id"] not in self.tree.sources
+ ):
+ self.tree.sources[source["id"]] = Source(
+ source, self.tree
+ )
for source_fid, change_message in quotes.items():
- self.sources.add(
- (self.tree.sources[source_fid], change_message)
- )
+ if source_fid in self.tree.sources:
+ self.sources.add(
+ (self.tree.sources[source_fid], change_message)
+ )
def get_notes(self):
"""retrieve marriage notes"""
--- /dev/null
+"""Tests based on live failures, fixes exceptions found in manual testing"""
+
+import pytest
+
+from getmyancestors.classes.tree.core import Fam, Tree
+
+
+def test_add_marriage_none_sources(mock_session):
+ """
+ Test that add_marriage handles cases where the sources fetch returns None.
+ This regression test ensures that a TypeError is not raised when sources is None.
+ """
+ tree = Tree(fs=mock_session)
+ tree.sources = {}
+
+ fam = Fam(tree=tree, num="F1")
+ fam.fid = "FAM123"
+
+ # Mock the sequence of calls:
+ # 1. Fetch relationship details (returns valid data with source references)
+ # 2. Fetch source descriptions (returns None, simulating the bug)
+ mock_session.get_url.side_effect = [
+ {
+ "relationships": [
+ {"facts": [], "sources": [{"descriptionId": "S1", "attribution": {}}]}
+ ]
+ },
+ None,
+ ]
+
+ # This should not parse TypeError
+ try:
+ fam.add_marriage("FAM123")
+ except TypeError:
+ pytest.fail("add_marriage raised TypeError when sources was None")
+
+
+def test_add_marriage_missing_source_key(mock_session):
+ """
+ Test that add_marriage verifies source existence in tree.sources before accessing it.
+ This prevents KeyErrors when a referenced source was not successfully fetched.
+ """
+ tree = Tree(fs=mock_session)
+ tree.sources = {}
+
+ fam = Fam(tree=tree, num="F1")
+ fam.fid = "FAM_KEYERROR"
+
+ source_id = "MISSING_SOURCE_ID"
+
+ # Mock the sequence:
+ # 1. Fetch relationship details (referencing a source)
+ # 2. Fetch source details (returns None, so the source is never added to tree.sources)
+ mock_session.get_url.side_effect = [
+ {
+ "relationships": [
+ {
+ "facts": [],
+ "sources": [
+ {
+ "descriptionId": source_id,
+ "attribution": {"changeMessage": "msg"},
+ }
+ ],
+ }
+ ]
+ },
+ None,
+ ]
+
+ # This should not raise KeyError
+ try:
+ fam.add_marriage("FAM_KEYERROR")
+ except KeyError:
+ pytest.fail(
+ "add_marriage raised KeyError when source_fid was missing from tree.sources"
+ )
+
+ # Verify that the invalid source was truly skipped
+ assert len(fam.sources) == 0
--- /dev/null
+
+
+from getmyancestors.classes.tree.core import Indi, Tree
+
+
+def test_indi_name_population(mock_session):
+ """
+ Test that Indi.add_data correctly populates self.name when a preferred name is present.
+ """
+ tree = Tree(fs=mock_session)
+ indi = Indi(fid="INDI123", tree=tree)
+
+ data = {
+ "living": False,
+ "names": [
+ {
+ "type": "http://gedcomx.org/BirthName",
+ "preferred": True,
+ "nameForms": [
+ {
+ "parts": [
+ {"type": "http://gedcomx.org/Given", "value": "John"},
+ {"type": "http://gedcomx.org/Surname", "value": "Doe"},
+ ]
+ }
+ ],
+ },
+ {
+ "type": "http://gedcomx.org/Nickname",
+ "preferred": False,
+ "nameForms": [
+ {"parts": [{"type": "http://gedcomx.org/Given", "value": "Johnny"}]}
+ ],
+ },
+ ],
+ }
+
+ indi.add_data(data)
+
+ assert indi.name is not None
+ assert indi.name.given == "John"
+ assert indi.name.surname == "Doe"
+ assert indi.name.kind == "birthname"
+
+ # Verify other names are still added
+ assert len(indi.nicknames) == 1
+ nickname = list(indi.nicknames)[0]
+ assert nickname.given == "Johnny"
+
+
+def test_indi_name_population_no_preferred(mock_session):
+ """
+ Test fallback behavior or at least ensure no crash if no preferred name (though typically FS provides one).
+ Logic dictates self.name might remain None or be set if logic allows (current logic only sets if not alt).
+ """
+ tree = Tree(fs=mock_session)
+ indi = Indi(fid="INDI124", tree=tree)
+
+ data = {
+ "living": False,
+ "names": [
+ {
+ "type": "http://gedcomx.org/BirthName",
+ "preferred": False, # All are alternate
+ "nameForms": [
+ {"parts": [{"type": "http://gedcomx.org/Given", "value": "John"}]}
+ ],
+ }
+ ],
+ }
+
+ indi.add_data(data)
+
+ # Based on current logic "if not alt and not self.name", self.name will remain None if all are alt.
+ # This is acceptable behavior for now, or we might want to relax it.
+ # For now, just asserting it doesn't crash.
+ assert indi.name is None
+ assert len(indi.birthnames) == 1
data_file = ".tmp/.coverage"
[tool.coverage.report]
-fail_under = 45.00
+fail_under = 67.98
precision = 2
show_missing = true
"**/tests/**" # do NOT show coverage tests... redundant
]
-exclude_lines = ["pragma: no cover"]
\ No newline at end of file
+exclude_lines = ["pragma: no cover"]
-Subproject commit b3084953c34c25c3867bcc1e000dfb32e59ae6f5
+Subproject commit 741c02ced0efd708044045e855cb7fafd6eb6aa8