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..944b6ec 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 and returns None values unchanged""" + 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..d5c1ce1 --- /dev/null +++ b/test/test_get_auth_factors_fix.py @@ -0,0 +1,142 @@ +# 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 +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 + + +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) + + 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() 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.