]> Nutra Git (v1) - gamesguru/getmyancestors.git/commitdiff
init tests
authorShane Jaroch <chown_tee@proton.me>
Sat, 27 Dec 2025 11:57:05 +0000 (06:57 -0500)
committerShane Jaroch <chown_tee@proton.me>
Sat, 27 Dec 2025 12:03:24 +0000 (07:03 -0500)
.envrc [new file with mode: 0644]
.requirements-lint.txt [new file with mode: 0644]
Makefile [new file with mode: 0644]
tests/conftest.py [new file with mode: 0644]
tests/test_cli.py [new file with mode: 0644]
tests/test_session.py [new file with mode: 0644]
tests/test_tree.py [new file with mode: 0644]

diff --git a/.envrc b/.envrc
new file mode 100644 (file)
index 0000000..1c21d04
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,2 @@
+source .venv/bin/activate
+unset PS1
diff --git a/.requirements-lint.txt b/.requirements-lint.txt
new file mode 100644 (file)
index 0000000..70606de
--- /dev/null
@@ -0,0 +1,8 @@
+black==25.1.0
+flake8==7.3.0
+isort==6.0.1
+mypy==1.17.1
+pylint==3.3.8
+types-requests==2.32.4.20250809
+coverage==7.13.0
+pytest==9.0.2
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..ac54164
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+LINT_LOCS ?= getmyancestors/
+
+.PHONY: lint
+lint:
+       flake8 $(LINT_LOCS)
+       black $(LINT_LOCS)
+       isort $(LINT_LOCS)
+       pylint $(LINT_LOCS)
+       mypy $(LINT_LOCS)
+
+.PHONY: test
+test:
+       coverage run -m pytest -svv tests/
+       coverage report -m
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..f6ae9a2
--- /dev/null
@@ -0,0 +1,39 @@
+from unittest.mock import MagicMock
+
+import pytest
+
+
+@pytest.fixture
+def mock_user_data():
+    return {
+        "users": [
+            {
+                "personId": "KW7V-Y32",
+                "preferredLanguage": "en",
+                "displayName": "Test User",
+            }
+        ]
+    }
+
+
+@pytest.fixture
+def mock_person_data():
+    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"}]}],
+            }
+        ]
+    }
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644 (file)
index 0000000..bbea8b9
--- /dev/null
@@ -0,0 +1,59 @@
+import sys
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from getmyancestors.getmyancestors import main
+
+
+class TestCLI:
+
+    @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 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()
+
+            main()
+
+            # Verify add_children was called (logic inside main triggers this based on args.descend)
+            assert mock_tree_instance.add_children.called
diff --git a/tests/test_session.py b/tests/test_session.py
new file mode 100644 (file)
index 0000000..edd7757
--- /dev/null
@@ -0,0 +1,85 @@
+from unittest.mock import MagicMock, patch
+
+import pytest
+from requests.exceptions import HTTPError
+
+from getmyancestors.classes.session import Session
+
+
+class TestSession:
+
+    @patch("requests.Session.get")
+    @patch("requests.Session.post")
+    def test_login_success(self, mock_post, mock_get):
+        """Test the full OAuth2 login flow with successful token retrieval."""
+        # Setup the sequence of responses for the login flow
+        mock_get.side_effect = [
+            MagicMock(cookies={"XSRF-TOKEN": "abc"}),  # 1. Initial page load
+            MagicMock(status_code=200),  # 3. Redirect URL page
+            MagicMock(
+                headers={"location": "http://callback?code=123"}
+            ),  # 4. Auth callback
+        ]
+
+        # Mock the JSON response for the login POST
+        mock_post.side_effect = [
+            MagicMock(json=lambda: {"redirectUrl": "http://auth.url"}),  # 2. Login POST
+            MagicMock(json=lambda: {"access_token": "fake_token"}),  # 5. Token POST
+        ]
+
+        # Initialize session (triggers login)
+        session = Session("user", "pass", verbose=True)
+
+        assert session.logged is True
+        assert session.headers["Authorization"] == "Bearer fake_token"
+
+    @patch("requests.Session.get")
+    @patch("requests.Session.post")
+    def test_login_failure_bad_creds(self, mock_post, mock_get):
+        """Test login failure when credentials are rejected."""
+        mock_get.return_value.cookies = {"XSRF-TOKEN": "abc"}
+
+        # Simulate login error response
+        mock_post.return_value.json.return_value = {"loginError": "Invalid credentials"}
+
+        session = Session("user", "badpass")
+
+        # Should not have session cookie or auth header
+        assert session.logged is False
+        assert "Authorization" not in session.headers
+
+    @patch("getmyancestors.classes.session.Session.login")  # Prevent auto-login in init
+    def test_get_url_401_retry(self, mock_login):
+        """Test that a 401 response triggers a re-login and retry."""
+        session = Session("u", "p")
+
+        # Mock Session.get directly on the instance to control responses
+        with patch.object(session, "get") as mock_request_get:
+            # First call 401, Second call 200 OK
+            mock_request_get.side_effect = [
+                MagicMock(status_code=401),
+                MagicMock(status_code=200, json=lambda: {"data": "success"}),
+            ]
+
+            result = session.get_url("/test-endpoint")
+
+            assert mock_login.call_count == 2  # Once init, Once after 401
+            assert result == {"data": "success"}
+
+    @patch("getmyancestors.classes.session.Session.login")
+    def test_get_url_403_ordinances(self, mock_login):
+        """Test handling of 403 Forbidden specifically for ordinances."""
+        session = Session("u", "p")
+
+        with patch.object(session, "get") as mock_request_get:
+            response_403 = MagicMock(status_code=403)
+            response_403.json.return_value = {
+                "errors": [{"message": "Unable to get ordinances."}]
+            }
+            response_403.raise_for_status.side_effect = HTTPError("403 Client Error")
+
+            mock_request_get.return_value = response_403
+
+            result = session.get_url("/test-ordinances")
+
+            assert result == "error"
diff --git a/tests/test_tree.py b/tests/test_tree.py
new file mode 100644 (file)
index 0000000..f2de14e
--- /dev/null
@@ -0,0 +1,68 @@
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from getmyancestors.classes.tree import Indi, Tree
+
+
+class TestTree:
+
+    @pytest.fixture
+    def mock_session(self, mock_user_data):
+        session = MagicMock()
+        session.fid = "KW7V-Y32"
+        session.get_url.return_value = mock_user_data
+        session._ = lambda s: s  # Mock translation identity function
+        return session
+
+    def test_add_indis(self, mock_session, mock_person_data):
+        """Test adding a list of individuals to the tree."""
+        tree = Tree(mock_session)
+
+        # Mock the API call for person details
+        mock_session.get_url.side_effect = [
+            mock_person_data,  # For person details
+            None,  # For child relationships (empty for this test)
+        ]
+
+        tree.add_indis(["KW7V-Y32"])
+
+        assert "KW7V-Y32" in tree.indi
+        person = tree.indi["KW7V-Y32"]
+        assert person.name == "John Doe"
+        assert person.sex == "M"
+
+    def test_add_parents(self, mock_session):
+        """Test fetching parents creates family links."""
+        tree = Tree(mock_session)
+        child_id = "KW7V-CHILD"
+        father_id = "KW7V-DAD"
+        mother_id = "KW7V-MOM"
+
+        # Seed child in tree
+        tree.indi[child_id] = Indi(child_id, tree)
+
+        # Mock parent relationship response
+        mock_session.get_url.return_value = {
+            "childAndParentsRelationships": [
+                {
+                    "father": {"resourceId": father_id},
+                    "mother": {"resourceId": mother_id},
+                    "fatherFacts": [{"type": "http://gedcomx.org/BiologicalParent"}],
+                    "motherFacts": [{"type": "http://gedcomx.org/BiologicalParent"}],
+                }
+            ]
+        }
+
+        # Mock fetching the actual parent person objects
+        # We patch add_indis to avoid the recursive fetch details logic for this unit test
+        with patch.object(tree, "add_indis") as mock_add_indis:
+            result = tree.add_parents({child_id})
+
+            assert father_id in result
+            assert mother_id in result
+
+            # Verify family object creation
+            fam_key = (tree.indi[father_id], tree.indi[mother_id])
+            assert fam_key in tree.fam
+            assert tree.indi[child_id] in tree.fam[fam_key].children