Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions onelogin/api/multi_factor_authentication_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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",
}

Expand Down
10 changes: 10 additions & 0 deletions onelogin/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
142 changes: 142 additions & 0 deletions test/test_get_auth_factors_fix.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Comment on lines +34 to +39
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test validates model deserialization but doesn't test the actual get_auth_factors API method. Consider adding an integration test that verifies the method returns List[GetAuthFactors200Response] when called with a mocked API response.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an integration test in commit 13d87c2 that verifies the get_auth_factors method returns List[GetAuthFactors200Response] with a mocked API response. The test also validates that the response_types_map is correctly set to 'List[GetAuthFactors200Response]'.

# 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()
17 changes: 17 additions & 0 deletions test/test_user_manager_user_id_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading