From d5ee719843b61b3fc925386b3dbc5580e5763697 Mon Sep 17 00:00:00 2001 From: Franccesco Orozco Date: Wed, 10 Dec 2025 18:32:47 -0600 Subject: [PATCH] fix(models): make Position.name optional to handle null API responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Bloom Growth API can return null for Position.name field, causing Pydantic validation errors in UserOperations methods like positions(), details(include_positions=True), and details(all=True). - Changed Position.name from required str to optional str | None - Added tests for positions() with null names - Added tests for details(all=True) with null position names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/bloomy/models.py | 2 +- tests/test_users.py | 57 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/bloomy/models.py b/src/bloomy/models.py index e55ef2a..01f5d4d 100644 --- a/src/bloomy/models.py +++ b/src/bloomy/models.py @@ -32,7 +32,7 @@ class Position(BloomyBaseModel): """Model for position information.""" id: int - name: str + name: str | None = None class UserDetails(BloomyBaseModel): diff --git a/tests/test_users.py b/tests/test_users.py index 5c7120b..c98101b 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -93,6 +93,40 @@ def test_details_with_all( assert len(result.positions) == 1 assert result.positions[0].name == "Manager" + def test_details_with_positions_null_name( + self, mock_http_client: Mock, sample_user_data: dict[str, Any] + ) -> None: + """Test getting user details with positions including null names.""" + # Mock responses + user_response = Mock() + user_response.json.return_value = sample_user_data + + reports_response = Mock() + reports_response.json.return_value = [] + + positions_response = Mock() + positions_response.json.return_value = [ + {"Group": {"Position": {"Id": 789, "Name": "Manager"}}}, + {"Group": {"Position": {"Id": 790, "Name": None}}}, + ] + + mock_http_client.get.side_effect = [ + user_response, + reports_response, + positions_response, + ] + + user_ops = UserOperations(mock_http_client) + result = user_ops.details(user_id=123, all=True) + + assert result.direct_reports is not None + assert result.positions is not None + assert len(result.positions) == 2 + assert result.positions[0].id == 789 + assert result.positions[0].name == "Manager" + assert result.positions[1].id == 790 + assert result.positions[1].name is None + def test_direct_reports(self, mock_http_client: Mock) -> None: """Test getting direct reports.""" mock_response = Mock() @@ -139,6 +173,29 @@ def test_positions(self, mock_http_client: Mock) -> None: mock_http_client.get.assert_called_once_with("users/123/seats") + def test_positions_with_null_name(self, mock_http_client: Mock) -> None: + """Test getting user positions when name is null.""" + mock_response = Mock() + mock_response.json.return_value = [ + {"Group": {"Position": {"Id": 101, "Name": None}}}, + {"Group": {"Position": {"Id": 102, "Name": "Team Lead"}}}, + {"Group": {"Position": {"Id": 103, "Name": None}}}, + ] + mock_http_client.get.return_value = mock_response + + user_ops = UserOperations(mock_http_client) + result = user_ops.positions(user_id=123) + + assert len(result) == 3 + assert result[0].id == 101 + assert result[0].name is None + assert result[1].id == 102 + assert result[1].name == "Team Lead" + assert result[2].id == 103 + assert result[2].name is None + + mock_http_client.get.assert_called_once_with("users/123/seats") + def test_search(self, mock_http_client: Mock) -> None: """Test searching users.""" mock_response = Mock()