From 72dbcd8307d6de62abb2fd02d5c3bd6491c3d8f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:54:58 +0000 Subject: [PATCH 1/4] Initial plan From 2aec7fc3c2ca381c3906120e7a5a1ff209888de2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:59:24 +0000 Subject: [PATCH 2/4] Fix type validation errors for manager_user_id and get_auth_factors - Add field validator to convert integer manager_user_id to string - Update get_auth_factors return type from single object to List - Add comprehensive tests for both fixes Co-authored-by: Subterrane <5290140+Subterrane@users.noreply.github.com> --- .../api/multi_factor_authentication_api.py | 8 +- onelogin/models/user.py | 10 ++ test/test_get_auth_factors_fix.py | 92 +++++++++++++++++++ test/test_user_manager_user_id_fix.py | 17 ++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 test/test_get_auth_factors_fix.py diff --git a/onelogin/api/multi_factor_authentication_api.py b/onelogin/api/multi_factor_authentication_api.py index 153597a..dca92ab 100644 --- a/onelogin/api/multi_factor_authentication_api.py +++ b/onelogin/api/multi_factor_authentication_api.py @@ -684,7 +684,7 @@ def generate_otp_with_http_info(self, user_id : Annotated[StrictInt, Field(..., _request_auth=_params.get('_request_auth')) @validate_call - def get_auth_factors(self, user_id : Annotated[StrictInt, Field(..., description="Set to the id of the user that you want to return.")], **kwargs) -> GetAuthFactors200Response: # noqa: E501 + def get_auth_factors(self, user_id : Annotated[StrictInt, Field(..., description="Set to the id of the user that you want to return.")], **kwargs) -> List[GetAuthFactors200Response]: # noqa: E501 """Get User Factors # noqa: E501 Get a user\\'s available authentication factors # noqa: E501 @@ -705,7 +705,7 @@ def get_auth_factors(self, user_id : Annotated[StrictInt, Field(..., description :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: GetAuthFactors200Response + :rtype: List[GetAuthFactors200Response] """ kwargs['_return_http_data_only'] = True if '_preload_content' in kwargs: @@ -747,7 +747,7 @@ def get_auth_factors_with_http_info(self, user_id : Annotated[StrictInt, Field(. :return: Returns the result object. If the method is called asynchronously, returns the request thread. - :rtype: tuple(GetAuthFactors200Response, status_code(int), headers(HTTPHeaderDict)) + :rtype: tuple(List[GetAuthFactors200Response], status_code(int), headers(HTTPHeaderDict)) """ _params = locals() @@ -802,7 +802,7 @@ def get_auth_factors_with_http_info(self, user_id : Annotated[StrictInt, Field(. _auth_settings = ['OAuth2'] # noqa: E501 _response_types_map = { - '200': "GetAuthFactors200Response", + '200': "List[GetAuthFactors200Response]", '401': "AltErr", } diff --git a/onelogin/models/user.py b/onelogin/models/user.py index 22a078b..899cd0d 100644 --- a/onelogin/models/user.py +++ b/onelogin/models/user.py @@ -81,6 +81,16 @@ def status_validate_enum(cls, value): raise ValueError("must be one of enum values (0, 1, 2, 3, 4, 5, 7, 8)") return value + @field_validator('manager_user_id', mode='before') + @classmethod + def manager_user_id_to_string(cls, value): + """Converts manager_user_id to string if it's an integer""" + if value is None: + return value + if isinstance(value, int): + return str(value) + return value + """Pydantic configuration""" model_config = { "validate_by_name": True, diff --git a/test/test_get_auth_factors_fix.py b/test/test_get_auth_factors_fix.py new file mode 100644 index 0000000..276f129 --- /dev/null +++ b/test/test_get_auth_factors_fix.py @@ -0,0 +1,92 @@ +# coding: utf-8 + +""" + OneLogin API + + OpenAPI Specification for OneLogin # noqa: E501 + + The version of the OpenAPI document: 3.1.1 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" + + +import unittest +import datetime + +import onelogin +from onelogin.models.get_auth_factors200_response import GetAuthFactors200Response +from onelogin.rest import ApiException + + +class TestGetAuthFactorsFix(unittest.TestCase): + """Test case for get_auth_factors return type fix""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_get_auth_factors_list_response(self): + """ + Test that GetAuthFactors200Response can be deserialized from a list. + This test validates the fix where API returns a list of factors + but model expected a single object. + """ + # Example response data from API (a list of factors) + factors_data = [ + { + "factor_id": 3098, + "name": "OneLogin SMS", + "auth_factor_name": "OneLogin SMS" + }, + { + "factor_id": 3099, + "name": "YubiKey", + "auth_factor_name": "YubiKey" + } + ] + + # Each item in the list should be deserializable as GetAuthFactors200Response + factors = [GetAuthFactors200Response.from_dict(factor_data) for factor_data in factors_data] + + self.assertEqual(len(factors), 2) + self.assertEqual(factors[0].factor_id, 3098) + self.assertEqual(factors[0].name, "OneLogin SMS") + self.assertEqual(factors[1].factor_id, 3099) + self.assertEqual(factors[1].name, "YubiKey") + + def test_single_auth_factor_from_dict(self): + """ + Test that a single auth factor can be created from dict. + """ + factor_data = { + "factor_id": 3098, + "name": "OneLogin SMS", + "auth_factor_name": "OneLogin SMS" + } + + factor = GetAuthFactors200Response.from_dict(factor_data) + self.assertEqual(factor.factor_id, 3098) + self.assertEqual(factor.name, "OneLogin SMS") + self.assertEqual(factor.auth_factor_name, "OneLogin SMS") + + def test_auth_factor_with_optional_fields(self): + """ + Test that auth factor works with optional fields. + """ + factor_data = { + "factor_id": 3098 + # name and auth_factor_name are optional + } + + factor = GetAuthFactors200Response.from_dict(factor_data) + self.assertEqual(factor.factor_id, 3098) + self.assertIsNone(factor.name) + self.assertIsNone(factor.auth_factor_name) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_user_manager_user_id_fix.py b/test/test_user_manager_user_id_fix.py index 80a3cb8..129d6f8 100644 --- a/test/test_user_manager_user_id_fix.py +++ b/test/test_user_manager_user_id_fix.py @@ -47,6 +47,23 @@ def test_manager_user_id_with_string_value(self): self.assertEqual(user.manager_user_id, "252462756") self.assertIsInstance(user.manager_user_id, str) + def test_manager_user_id_with_integer_value(self): + """ + Test that manager_user_id accepts integer values and converts them to string. + This test validates the fix where API sometimes returns manager_user_id as integer. + """ + user_data = { + "id": 123, + "username": "testuser", + "email": "test@example.com", + "manager_user_id": 252462756 # Integer value as sometimes returned by API + } + + # This should work without validation errors and convert to string + user = User.from_dict(user_data) + self.assertEqual(user.manager_user_id, "252462756") + self.assertIsInstance(user.manager_user_id, str) + def test_manager_user_id_with_none_value(self): """ Test that manager_user_id accepts None values. From 6cdf278ff7dec9866c7a2cce6d8e2095099f8a7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:01:55 +0000 Subject: [PATCH 3/4] Address code review feedback - add trailing comma for better style Co-authored-by: Subterrane <5290140+Subterrane@users.noreply.github.com> --- test/test_get_auth_factors_fix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_get_auth_factors_fix.py b/test/test_get_auth_factors_fix.py index 276f129..0d155c9 100644 --- a/test/test_get_auth_factors_fix.py +++ b/test/test_get_auth_factors_fix.py @@ -78,7 +78,7 @@ def test_auth_factor_with_optional_fields(self): Test that auth factor works with optional fields. """ factor_data = { - "factor_id": 3098 + "factor_id": 3098, # name and auth_factor_name are optional } From 13d87c245c83f330a2e43972574b0e6561074f57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:47:39 +0000 Subject: [PATCH 4/4] Address PR review comments - Improve docstring for manager_user_id validator to mention None handling - Add integration test for get_auth_factors API method with mocked response Co-authored-by: Subterrane <5290140+Subterrane@users.noreply.github.com> --- onelogin/models/user.py | 2 +- test/test_get_auth_factors_fix.py | 50 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/onelogin/models/user.py b/onelogin/models/user.py index 899cd0d..944b6ec 100644 --- a/onelogin/models/user.py +++ b/onelogin/models/user.py @@ -84,7 +84,7 @@ def status_validate_enum(cls, value): @field_validator('manager_user_id', mode='before') @classmethod def manager_user_id_to_string(cls, value): - """Converts manager_user_id to string if it's an integer""" + """Converts manager_user_id to string if it's an integer and returns None values unchanged""" if value is None: return value if isinstance(value, int): diff --git a/test/test_get_auth_factors_fix.py b/test/test_get_auth_factors_fix.py index 0d155c9..d5c1ce1 100644 --- a/test/test_get_auth_factors_fix.py +++ b/test/test_get_auth_factors_fix.py @@ -14,9 +14,11 @@ import unittest import datetime +from unittest.mock import Mock, patch import onelogin from onelogin.models.get_auth_factors200_response import GetAuthFactors200Response +from onelogin.api.multi_factor_authentication_api import MultiFactorAuthenticationApi from onelogin.rest import ApiException @@ -87,6 +89,54 @@ def test_auth_factor_with_optional_fields(self): self.assertIsNone(factor.name) self.assertIsNone(factor.auth_factor_name) + def test_get_auth_factors_api_method_returns_list(self): + """ + Integration test that verifies the get_auth_factors API method + returns List[GetAuthFactors200Response] when called with a mocked API response. + This validates the fix for the return type change. + """ + # Create list of GetAuthFactors200Response objects + factor1 = GetAuthFactors200Response.from_dict({ + "factor_id": 3098, + "name": "OneLogin SMS", + "auth_factor_name": "OneLogin SMS" + }) + factor2 = GetAuthFactors200Response.from_dict({ + "factor_id": 3099, + "name": "YubiKey", + "auth_factor_name": "YubiKey" + }) + expected_result = [factor1, factor2] + + # Create API instance + api = MultiFactorAuthenticationApi() + + # Mock the api_client.call_api method to return deserialized objects + with patch.object(api.api_client, 'call_api', return_value=expected_result) as mock_call_api: + # Call the method + result = api.get_auth_factors(user_id=123) + + # Verify the result is a list + self.assertIsInstance(result, list) + self.assertEqual(len(result), 2) + + # Verify each item is a GetAuthFactors200Response instance + self.assertIsInstance(result[0], GetAuthFactors200Response) + self.assertIsInstance(result[1], GetAuthFactors200Response) + + # Verify the data + self.assertEqual(result[0].factor_id, 3098) + self.assertEqual(result[0].name, "OneLogin SMS") + self.assertEqual(result[1].factor_id, 3099) + self.assertEqual(result[1].name, "YubiKey") + + # Verify call_api was called with correct parameters including the List response type + mock_call_api.assert_called_once() + call_args = mock_call_api.call_args + # Check that response_types_map includes List[GetAuthFactors200Response] + response_types_map = call_args[1]['response_types_map'] + self.assertEqual(response_types_map['200'], 'List[GetAuthFactors200Response]') + if __name__ == '__main__': unittest.main()