From 7c83137a1543c2d1c03dae75873b590a884a4c76 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Sat, 27 Dec 2025 07:04:15 -0500 Subject: [PATCH] less mocks in tests --- tests/conftest.py | 88 +++++++++++++++++++++++++++++-------------- tests/test_cli.py | 88 +++++++++++++++++++------------------------ tests/test_parsing.py | 57 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 78 deletions(-) create mode 100644 tests/test_parsing.py diff --git a/tests/conftest.py b/tests/conftest.py index f6ae9a2..fe6c097 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,39 +1,69 @@ +import pytest from unittest.mock import MagicMock +import sys +import os -import pytest +# Ensure we can import the module from the root directory +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from getmyancestors.classes.session import Session @pytest.fixture -def mock_user_data(): - return { - "users": [ - { - "personId": "KW7V-Y32", - "preferredLanguage": "en", - "displayName": "Test User", - } - ] - } +def mock_session(): + """ + Creates a Session object where the network layer is mocked out. + """ + # Create the session but suppress the automatic login() call in __init__ + # We do this by mocking the login method *before* instantiation + with pytest.helpers.patch_method(Session, 'login'): + session = Session("test_user", "test_pass", verbose=False) + + # Manually set logged status to True so checks pass + # We need to mock the cookies since 'logged' property checks for 'fssessionid' + session.cookies = {"fssessionid": "mock_session_id"} + # Mock the request methods + session.get = MagicMock() + session.post = MagicMock() + + # Mock the internal translation method to just return the string + session._ = lambda s: s + + return session @pytest.fixture -def mock_person_data(): +def sample_person_json(): + """Returns a raw JSON response representing a Person from FamilySearch""" return { - "persons": [ - { - "id": "KW7V-Y32", - "display": { - "name": "John Doe", - "gender": "Male", - "lifespan": "1900-1980", - }, - "facts": [ - { - "type": "http://gedcomx.org/Birth", - "date": {"original": "1 Jan 1900"}, - } - ], - "names": [{"nameForms": [{"fullText": "John Doe"}]}], - } - ] + "persons": [{ + "id": "KW7V-Y32", + "display": { + "name": "John Doe", + "gender": "Male", + "lifespan": "1900-1980" + }, + "facts": [ + { + "type": "http://gedcomx.org/Birth", + "date": {"original": "1 Jan 1900"}, + "place": {"original": "New York"} + } + ], + "names": [ + { + "nameForms": [{"fullText": "John Doe"}] + } + ] + }] } + +# Helper to patch methods cleanly in fixtures +class Helpers: + @staticmethod + def patch_method(cls, method_name): + from unittest.mock import patch + return patch.object(cls, method_name) + +@pytest.fixture +def helpers(): + return Helpers diff --git a/tests/test_cli.py b/tests/test_cli.py index bbea8b9..f2b086a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,59 +1,49 @@ +import pytest import sys -from unittest.mock import MagicMock, patch +from unittest.mock import patch, MagicMock +from getmyancestors.getmyancestors import main -import pytest +class TestCLI: -from getmyancestors.getmyancestors import main + @patch('getmyancestors.getmyancestors.Session') + @patch('getmyancestors.getmyancestors.Tree') + def test_basic_args(self, MockTree, MockSession): + """Test that arguments are parsed and passed to classes correctly""" + # Mock sys.argv to simulate command line execution + test_args = [ + "getmyancestors", + "-u", "myuser", + "-p", "mypass", + "-i", "KW7V-Y32", + "--verbose" + ] -class TestCLI: + # Setup the session to appear logged in + MockSession.return_value.logged = True + + with patch.object(sys, 'argv', test_args): + main() - @patch("getmyancestors.getmyancestors.Session") - @patch("getmyancestors.getmyancestors.Tree") - @patch( - "sys.argv", - ["getmyancestors", "-u", "testuser", "-p", "testpass", "-i", "KW7V-Y32"], - ) - def test_main_execution(self, mock_tree, mock_session): - """Test that main runs with basic arguments.""" - mock_fs = mock_session.return_value - mock_fs.logged = True - - # Run main - main() - - # Verify Session initialized with args - mock_session.assert_called_with( - username="testuser", - password="testpass", - client_id=None, - redirect_uri=None, - verbose=False, - logfile=False, - timeout=60, + # Verify Session was initialized with CLI args + MockSession.assert_called_with( + "myuser", + "mypass", + None, # client_id (default) + None, # redirect_uri (default) + True, # verbose + False, # logfile + 60 # timeout ) - # Verify Tree operations - mock_tree.return_value.add_indis.assert_called() - mock_tree.return_value.print.assert_called() - - @patch("getmyancestors.getmyancestors.Session") - @patch( - "sys.argv", - ["getmyancestors", "-u", "testuser", "-p", "testpass", "--descend", "2"], - ) - def test_descend_argument(self, mock_session): - """Test that the descend argument is passed to logic.""" - mock_fs = mock_session.return_value - mock_fs.logged = True - - # We need to mock Tree because main interacts with it deeply - with patch("getmyancestors.getmyancestors.Tree") as mock_tree: - mock_tree_instance = mock_tree.return_value - # Return empty sets to stop loops - mock_tree_instance.add_children.return_value = set() + # Verify Tree started + MockTree.return_value.add_indis.assert_called_with(["KW7V-Y32"]) - main() + def test_arg_validation(self): + """Test that invalid ID formats cause an exit""" + test_args = ["getmyancestors", "-u", "u", "-p", "p", "-i", "BAD_ID"] - # Verify add_children was called (logic inside main triggers this based on args.descend) - assert mock_tree_instance.add_children.called + with patch.object(sys, 'argv', test_args): + with pytest.raises(SystemExit): + # This should trigger sys.exit("Invalid FamilySearch ID...") + main() diff --git a/tests/test_parsing.py b/tests/test_parsing.py new file mode 100644 index 0000000..98c450a --- /dev/null +++ b/tests/test_parsing.py @@ -0,0 +1,57 @@ +import pytest +from unittest.mock import MagicMock +from getmyancestors.classes.tree import Tree, Indi, Fam + +class TestDataParsing: + + def test_individual_parsing(self, mock_session, sample_person_json): + """ + Verify that raw JSON from FamilySearch is correctly parsed into an Indi object. + """ + # Setup the mock to return our sample JSON when get_url is called + mock_session.get_url = MagicMock(return_value=sample_person_json) + + tree = Tree(mock_session) + + # Act: Add the individual + tree.add_indis(["KW7V-Y32"]) + + # Assert: Check if the individual exists in the tree + assert "KW7V-Y32" in tree.indi + person = tree.indi["KW7V-Y32"] + + # Assert: Check attributes + assert person.name == "John Doe" + assert person.sex == "M" + assert person.fid == "KW7V-Y32" + + # Check if Birth fact was parsed (this tests your Fact class logic implicitly) + birth_fact = next((f for f in person.facts if f.tag == "BIRT"), None) + assert birth_fact is not None + assert birth_fact.date == "1 Jan 1900" + assert birth_fact.place == "New York" + + def test_family_linking(self, mock_session): + """ + Verify that ensure_family links husband and wife correctly. + """ + tree = Tree(mock_session) + + # Create dummy individuals + husb = Indi("HUSB01", tree) + wife = Indi("WIFE01", tree) + + # Create family + fam = tree.ensure_family(husb, wife) + + # Assertions + assert fam.husband == husb + assert fam.wife == wife + + # Check that the individuals know about the family + assert fam in husb.fams + assert fam in wife.fams + + # Ensure creating the same family again returns the same object + fam2 = tree.ensure_family(husb, wife) + assert fam is fam2 -- 2.52.0