diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 37d08c0..8909d6e 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -12,6 +12,9 @@ jobs: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] python-version: [ 3.8, 3.12 ] + # test with robot without and with Secret support? Not sure if + # it is worth it? + robot-version: [ 7.3.2, 7.4 ] steps: - uses: actions/checkout@v4 - name: Set up Python @@ -23,6 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install -e .[test] + python -m pip install robotframework==${{ matrix.robot-version }} - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names @@ -59,5 +63,5 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: rf-tests-report-${{ matrix.os }}-${{ matrix.python-version }} + name: rf-tests-report-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.robot-version }} path: ./tests-report diff --git a/.gitignore b/.gitignore index eb382b6..4d4d4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ env/* # ignore http server log atests/http_server/http_server.log +.claude/ diff --git a/atests/secretvar.py b/atests/secretvar.py new file mode 100644 index 0000000..0139422 --- /dev/null +++ b/atests/secretvar.py @@ -0,0 +1,7 @@ +# inject secret into robot suite.. doing this via python +# to ensure this can also run in older robot versions +try: + from robot.api.types import Secret + SECRET_PASSWORD = Secret("secret_passwd") +except (ImportError, ModuleNotFoundError): + SECRET_PASSWORD = "not-supported" diff --git a/atests/test_authentication.robot b/atests/test_authentication.robot index 96430c9..a15ad37 100644 --- a/atests/test_authentication.robot +++ b/atests/test_authentication.robot @@ -2,7 +2,7 @@ Library RequestsLibrary Library customAuthenticator.py Resource res_setup.robot - +Variables secretvar.py *** Test Cases *** Get With Auth @@ -23,12 +23,45 @@ Get With Custom Auth Get With Digest Auth [Tags] get get-cert - ${auth}= Create List user pass + ${auth}= Create List user passwd Create Digest Session ... authsession ... ${HTTP_LOCAL_SERVER} ... auth=${auth} ... debug=3 - ${resp}= GET On Session authsession /digest-auth/auth/user/pass + ${resp}= GET On Session authsession /digest-auth/auth/user/passwd + Should Be Equal As Strings ${resp.status_code} 200 + Should Be Equal As Strings ${resp.json()['authenticated']} True + +Get With Auth with Robot Secrets + [Tags] robot-74 get get-cert + Skip If $SECRET_PASSWORD == "not-supported" + ... msg=robot version does not support secrets + ${auth}= Create List user ${SECRET_PASSWORD} + Create Session authsession ${HTTP_LOCAL_SERVER} auth=${auth} + ${resp}= GET On Session authsession /basic-auth/user/secret_passwd + Should Be Equal As Strings ${resp.status_code} 200 + Should Be Equal As Strings ${resp.json()['authenticated']} True + +Get With Digest Auth with Robot Secrets + [Tags] robot-74 get get-cert + Skip If $SECRET_PASSWORD == "not-supported" + ... msg=robot version does not support secrets + ${auth}= Create List user ${SECRET_PASSWORD} + Create Digest Session + ... authsession + ... ${HTTP_LOCAL_SERVER} + ... auth=${auth} + ... debug=3 + ${resp}= GET On Session authsession /digest-auth/auth/user/secret_passwd + Should Be Equal As Strings ${resp.status_code} 200 + Should Be Equal As Strings ${resp.json()['authenticated']} True + +Session-less GET With Auth with Robot Secrets + [Tags] robot-74 get get-cert session-less + Skip If $SECRET_PASSWORD == "not-supported" + ... msg=robot version does not support secrets + ${auth}= Create List user ${SECRET_PASSWORD} + ${resp}= GET ${HTTP_LOCAL_SERVER}/basic-auth/user/secret_passwd auth=${auth} Should Be Equal As Strings ${resp.status_code} 200 Should Be Equal As Strings ${resp.json()['authenticated']} True diff --git a/src/RequestsLibrary/RequestsKeywords.py b/src/RequestsLibrary/RequestsKeywords.py index c74bf83..38fa90d 100644 --- a/src/RequestsLibrary/RequestsKeywords.py +++ b/src/RequestsLibrary/RequestsKeywords.py @@ -6,6 +6,7 @@ from RequestsLibrary import log from RequestsLibrary.compat import urljoin from RequestsLibrary.utils import ( + check_and_process_secrets, is_list_or_tuple, is_file_descriptor, warn_if_equal_symbol_in_url_session_less, @@ -22,6 +23,14 @@ def __init__(self): self.timeout = None self.cookies = None self.last_response = None + self._request_has_secrets = False + self._session_secrets = {} # Maps session object ID to secrets flag + + def _get_session_secrets_flag(self, session): + """Get the secrets flag for a session object""" + if not session: + return False + return self._session_secrets.get(id(session), False) def _common_request(self, method, session, uri, **kwargs): @@ -30,6 +39,19 @@ def _common_request(self, method, session, uri, **kwargs): else: request_function = getattr(requests, "request") + auth = kwargs.get("auth") + if auth is not None and isinstance(auth, (list, tuple)): + kwargs["auth"], contains_secrets = check_and_process_secrets(auth) + else: + contains_secrets = False + + if session: + # Check if the session was created with robot secrets + contains_secrets = contains_secrets or self._get_session_secrets_flag(session) + + # Store secrets flag for _print_debug to access + self._request_has_secrets = contains_secrets + self._capture_output() resp = request_function( @@ -40,7 +62,7 @@ def _common_request(self, method, session, uri, **kwargs): **kwargs ) - log.log_request(resp) + log.log_request(resp, has_secrets=contains_secrets) self._print_debug() log.log_response(resp) @@ -59,7 +81,7 @@ def _close_file_descriptors(files, data): """ Helper method that closes any open file descriptors. """ - + if is_list_or_tuple(files): files_descriptor_to_close = filter( is_file_descriptor, [file[1][1] for file in files] + [data] @@ -68,10 +90,10 @@ def _close_file_descriptors(files, data): files_descriptor_to_close = filter( is_file_descriptor, list(files.values()) + [data] ) - + for file_descriptor in files_descriptor_to_close: file_descriptor.close() - + @staticmethod def _merge_url(session, uri): """ diff --git a/src/RequestsLibrary/SessionKeywords.py b/src/RequestsLibrary/SessionKeywords.py index 0b19cb7..07518b9 100644 --- a/src/RequestsLibrary/SessionKeywords.py +++ b/src/RequestsLibrary/SessionKeywords.py @@ -1,4 +1,5 @@ import logging +import re import sys import requests @@ -12,7 +13,8 @@ from RequestsLibrary import utils from RequestsLibrary.compat import RetryAdapter, httplib from RequestsLibrary.exceptions import InvalidExpectedStatus, InvalidResponse -from RequestsLibrary.utils import is_string_type +from RequestsLibrary.log import AUTHORIZATION +from RequestsLibrary.utils import is_string_type, check_and_process_secrets from .RequestsKeywords import RequestsKeywords @@ -25,6 +27,10 @@ class SessionKeywords(RequestsKeywords): DEFAULT_RETRY_METHOD_LIST = RetryAdapter.get_default_allowed_methods() + def _set_session_secrets_flag(self, session, has_secrets): + """Store the secrets flag for a session object using its id as key""" + self._session_secrets[id(session)] = has_secrets + def _create_session( self, alias, @@ -172,7 +178,11 @@ def create_session( Note that max_retries must be greater than 0. """ - auth = requests.auth.HTTPBasicAuth(*auth) if auth else None + if auth: + processed_auth, session_has_secrets = check_and_process_secrets(auth) + auth = requests.auth.HTTPBasicAuth(*processed_auth) + else: + session_has_secrets = False logger.info( "Creating Session using : alias=%s, url=%s, headers=%s, \ @@ -180,7 +190,7 @@ def create_session( debug=%s " % (alias, url, headers, cookies, auth, timeout, proxies, verify, debug) ) - return self._create_session( + session = self._create_session( alias=alias, url=url, headers=headers, @@ -196,6 +206,8 @@ def create_session( retry_status_list=retry_status_list, retry_method_list=retry_method_list, ) + self._set_session_secrets_flag(session, session_has_secrets) + return session @keyword("Create Client Cert Session") def create_client_cert_session( @@ -262,7 +274,11 @@ def create_client_cert_session( eg. set to [502, 503] to retry requests if those status are returned. Note that max_retries must be greater than 0. """ - auth = requests.auth.HTTPBasicAuth(*auth) if auth else None + if auth: + processed_auth, session_has_secrets = check_and_process_secrets(auth) + auth = requests.auth.HTTPBasicAuth(*processed_auth) + else: + session_has_secrets = False logger.info( "Creating Session using : alias=%s, url=%s, headers=%s, \ @@ -300,6 +316,7 @@ def create_client_cert_session( ) session.cert = tuple(client_certs) + self._set_session_secrets_flag(session, session_has_secrets) return session @keyword("Create Custom Session") @@ -452,9 +469,14 @@ def create_digest_session( eg. set to [502, 503] to retry requests if those status are returned. Note that max_retries must be greater than 0. """ - digest_auth = requests.auth.HTTPDigestAuth(*auth) if auth else None + if auth: + processed_auth, session_has_secrets = check_and_process_secrets(auth) + digest_auth = requests.auth.HTTPDigestAuth(*processed_auth) + else: + digest_auth = None + session_has_secrets = False - return self._create_session( + session = self._create_session( alias=alias, url=url, headers=headers, @@ -470,6 +492,8 @@ def create_digest_session( retry_status_list=retry_status_list, retry_method_list=retry_method_list, ) + self._set_session_secrets_flag(session, session_has_secrets) + return session @keyword("Create Ntlm Session") def create_ntlm_session( @@ -543,7 +567,8 @@ def create_ntlm_session( " - expected 3, got {}".format(len(auth)) ) else: - ntlm_auth = HttpNtlmAuth("{}\\{}".format(auth[0], auth[1]), auth[2]) + processed_auth, session_has_secrets = check_and_process_secrets(auth) + ntlm_auth = HttpNtlmAuth("{}\\{}".format(processed_auth[0], processed_auth[1]), processed_auth[2]) logger.info( "Creating NTLM Session using : alias=%s, url=%s, \ headers=%s, cookies=%s, ntlm_auth=%s, timeout=%s, \ @@ -561,7 +586,7 @@ def create_ntlm_session( ) ) - return self._create_session( + session = self._create_session( alias=alias, url=url, headers=headers, @@ -577,6 +602,8 @@ def create_ntlm_session( retry_status_list=retry_status_list, retry_method_list=retry_method_list, ) + self._set_session_secrets_flag(session, session_has_secrets) + return session @keyword("Session Exists") def session_exists(self, alias): @@ -656,4 +683,14 @@ def _print_debug(self): debug_info = "\n".join( [ll.rstrip() for ll in debug_info.splitlines() if ll.strip()] ) + + # Mask Authorization header in debug output when secrets are used + if self._request_has_secrets: + debug_info = re.sub( + rf'({AUTHORIZATION}:)\s*([^\n]+)', + r'\1 *****', + debug_info, + flags=re.IGNORECASE + ) + logger.debug(debug_info) diff --git a/src/RequestsLibrary/log.py b/src/RequestsLibrary/log.py index 09807f4..d12ba23 100644 --- a/src/RequestsLibrary/log.py +++ b/src/RequestsLibrary/log.py @@ -17,7 +17,7 @@ def log_response(response): ) -def log_request(response): +def log_request(response, has_secrets=False): request = response.request if response.history: original_request = response.history[0].request @@ -25,9 +25,14 @@ def log_request(response): else: original_request = request redirected = "" + + # Mask Authorization header based on whether secrets were used safe_headers = dict(original_request.headers) - if logger.LOGLEVEL not in ['TRACE', 'DEBUG'] and AUTHORIZATION in safe_headers: - safe_headers[AUTHORIZATION] = '*****' + if AUTHORIZATION in safe_headers: + # If secrets were used, always mask. Otherwise, only mask if not in DEBUG/TRACE + if has_secrets or logger.LOGLEVEL not in ['TRACE', 'DEBUG']: + safe_headers[AUTHORIZATION] = '*****' + logger.info( "%s Request : " % original_request.method.upper() + "url=%s %s\n " % (original_request.url, redirected) diff --git a/src/RequestsLibrary/utils.py b/src/RequestsLibrary/utils.py index 78242b0..c7ee0d5 100644 --- a/src/RequestsLibrary/utils.py +++ b/src/RequestsLibrary/utils.py @@ -5,6 +5,11 @@ from requests.status_codes import codes from requests.structures import CaseInsensitiveDict from robot.api import logger +try: + from robot.api.types import Secret + robot_supports_secrets = True +except (ImportError, ModuleNotFoundError): + robot_supports_secrets = False from RequestsLibrary.compat import urlencode from RequestsLibrary.exceptions import UnknownStatusError @@ -74,9 +79,35 @@ def is_string_type(data): def is_file_descriptor(fd): return isinstance(fd, io.IOBase) + def is_list_or_tuple(data): return isinstance(data, (list, tuple)) + +def check_and_process_secrets(auth): + """ + Check if auth contains secrets and process them + + Returns: + tuple: (processed_auth, has_secrets_flag) + """ + if not auth or not isinstance(auth, (list, tuple)): + return auth, False + + if robot_supports_secrets: + has_secrets_flag = False + processed = [] + for a in auth: + if isinstance(a, Secret): + has_secrets_flag = True + processed.append(a.value) + else: + processed.append(a) + return tuple(processed), has_secrets_flag + else: + return auth, False + + def utf8_urlencode(data): if is_string_type(data): return data.encode("utf-8") diff --git a/utests/test_session_secrets.py b/utests/test_session_secrets.py new file mode 100644 index 0000000..f523765 --- /dev/null +++ b/utests/test_session_secrets.py @@ -0,0 +1,289 @@ +import pytest + +from RequestsLibrary import RequestsLibrary +from utests import mock + +try: + from robot.api.types import Secret + secret_type_supported = True +except (ImportError, ModuleNotFoundError): + secret_type_supported = False + + +def test_get_session_secrets_flag_with_none_session(): + """Test _get_session_secrets_flag returns False for None session""" + lib = RequestsLibrary() + assert lib._get_session_secrets_flag(None) is False + + +def test_get_session_secrets_flag_for_session_without_secrets(): + """Test _get_session_secrets_flag returns False for session created without secrets""" + lib = RequestsLibrary() + session = lib.create_session('test', 'http://example.com') + # Session created without secrets should return False + assert lib._get_session_secrets_flag(session) is False + + +def test_get_session_secrets_flag_for_unknown_session(): + """Test _get_session_secrets_flag returns False for untracked session object""" + lib = RequestsLibrary() + # Create a session but don't register it + import requests + untracked_session = requests.Session() + # Unknown session should return False + assert lib._get_session_secrets_flag(untracked_session) is False + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_create_session_with_secrets_sets_flag(): + """Test that creating a session with secrets properly sets the flag""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + auth = ['user', secret_pwd] + + # Create session with secrets + session = lib.create_session('test', 'http://example.com', auth=auth) + + # Verify secret flag is tracked + assert lib._get_session_secrets_flag(session) is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_create_digest_session_with_secrets_sets_flag(): + """Test that creating a digest session with secrets properly sets the flag""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + auth = ['user', secret_pwd] + + # Create digest session with secrets + session = lib.create_digest_session('test', 'http://example.com', auth=auth) + + # Verify secret flag is tracked + assert lib._get_session_secrets_flag(session) is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_create_client_cert_session_with_secrets_sets_flag(): + """Test that creating a client cert session with secrets properly sets the flag""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + auth = ['user', secret_pwd] + + # Create client cert session with secrets + session = lib.create_client_cert_session( + 'test', 'http://example.com', + auth=auth, + client_certs=('cert.pem', 'key.pem') + ) + + # Verify secret flag is tracked + assert lib._get_session_secrets_flag(session) is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_common_request_with_session_secrets_sets_request_flag(): + """Test _common_request properly checks session secret flag and sets request flag""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + + # Create session with secrets in auth + session = lib.create_session('test', 'http://example.com', auth=['user', secret_pwd]) + + # Mock the session.request to prevent actual HTTP call + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + session.request = mock.MagicMock(return_value=mock_response) + + # Make a request without any auth parameter + lib._common_request('GET', session, '/test') + + # Verify _request_has_secrets is set to True due to session having secrets + assert lib._request_has_secrets is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_common_request_without_session_secrets_does_not_set_request_flag(): + """Test _common_request doesn't set request flag when session has no secrets""" + lib = RequestsLibrary() + + # Create session without secrets + session = lib.create_session('test', 'http://example.com', auth=['user', 'plaintext']) + + # Mock the session.request to prevent actual HTTP call + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + session.request = mock.MagicMock(return_value=mock_response) + + # Make a request without any auth parameter + lib._common_request('GET', session, '/test') + + # Verify _request_has_secrets is False + assert lib._request_has_secrets is False + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_common_request_with_request_level_secrets(): + """Test _common_request sets flag when secrets are in request auth (not session)""" + lib = RequestsLibrary() + request_secret = Secret('request_password') + + # Create session without secrets + session = lib.create_session('test', 'http://example.com') + + # Mock the session.request to prevent actual HTTP call + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + session.request = mock.MagicMock(return_value=mock_response) + + # Make a request with secrets in auth parameter + lib._common_request('GET', session, '/test', auth=['user', request_secret]) + + # Verify _request_has_secrets is True due to request auth having secrets + assert lib._request_has_secrets is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_common_request_with_both_session_and_request_secrets(): + """Test _common_request with secrets in both session and request auth""" + lib = RequestsLibrary() + session_secret = Secret('session_pwd') + request_secret = Secret('request_pwd') + + # Create session with secrets + session = lib.create_session('test', 'http://example.com', auth=['user1', session_secret]) + + # Mock the session.request to prevent actual HTTP call + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + session.request = mock.MagicMock(return_value=mock_response) + + # Make a request with different secrets in auth + lib._common_request('GET', session, '/test', auth=['user2', request_secret]) + + # Verify _request_has_secrets is True (secrets from both sources) + assert lib._request_has_secrets is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_session_secrets_flag_persists_across_requests(): + """Test that session secret flag persists across multiple requests""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + + # Create session with secrets + session = lib.create_session('test', 'http://example.com', auth=['user', secret_pwd]) + + # Mock the session.request + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + session.request = mock.MagicMock(return_value=mock_response) + + # Make first request + lib._common_request('GET', session, '/test1') + assert lib._request_has_secrets is True + + # Make second request + lib._common_request('GET', session, '/test2') + assert lib._request_has_secrets is True + + # Session should still have the flag set + assert lib._get_session_secrets_flag(session) is True + + +def test_common_request_sessionless_with_no_secrets(): + """Test session-less request without secrets doesn't set flag""" + lib = RequestsLibrary() + + # Mock requests.request for session-less call + with mock.patch('requests.request') as mock_request: + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + mock_request.return_value = mock_response + + # Make session-less request with plain auth + lib._common_request('GET', None, 'http://example.com/test', auth=['user', 'password']) + + # Should not have secrets flag set + assert lib._request_has_secrets is False + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_common_request_sessionless_with_secrets(): + """Test session-less request with secrets sets flag""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + + # Mock requests.request for session-less call + with mock.patch('requests.request') as mock_request: + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.text = '' + mock_response.content = b'' + mock_response.url = 'http://example.com/test' + mock_request.return_value = mock_response + + # Make session-less request with secret auth + lib._common_request('GET', None, 'http://example.com/test', auth=['user', secret_pwd]) + + # Should have secrets flag set + assert lib._request_has_secrets is True + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_multiple_sessions_with_different_secret_flags(): + """Test that multiple sessions can have different secret flags""" + lib = RequestsLibrary() + secret_pwd = Secret('mypassword') + + # Create session with secrets + session1 = lib.create_session('session1', 'http://example1.com', auth=['user', secret_pwd]) + + # Create session without secrets + session2 = lib.create_session('session2', 'http://example2.com', auth=['user', 'plaintext']) + + # Verify flags are tracked independently + assert lib._get_session_secrets_flag(session1) is True + assert lib._get_session_secrets_flag(session2) is False + + +@pytest.mark.skipif(not secret_type_supported, reason="Requires Robot 7.4+") +def test_set_session_secrets_flag_directly(): + """Test _set_session_secrets_flag method directly""" + lib = RequestsLibrary() + session = lib.create_session('test', 'http://example.com') + + # Initially should be False + assert lib._get_session_secrets_flag(session) is False + + # Set to True + lib._set_session_secrets_flag(session, True) + assert lib._get_session_secrets_flag(session) is True + + # Set back to False + lib._set_session_secrets_flag(session, False) + assert lib._get_session_secrets_flag(session) is False diff --git a/utests/test_utils.py b/utests/test_utils.py index 0ae280d..46182ae 100644 --- a/utests/test_utils.py +++ b/utests/test_utils.py @@ -4,10 +4,16 @@ from requests import Session from RequestsLibrary import RequestsLibrary -from RequestsLibrary.utils import is_file_descriptor, merge_headers +from RequestsLibrary.utils import is_file_descriptor, merge_headers, check_and_process_secrets from utests import SCRIPT_DIR from utests import mock +try: + from robot.api.types import Secret + secret_type_supported = True +except (ImportError, ModuleNotFoundError): + secret_type_supported = False + def test_none(): assert is_file_descriptor(None) is False @@ -72,3 +78,44 @@ def test_warn_that_url_is_missing(mocked_logger, mocked_keywords): except TypeError: pass mocked_logger.warn.assert_called() + + +def test_check_and_process_secrets_with_no_secrets(): + auth = ('user', 'password') + processed_auth, has_secrets = check_and_process_secrets(auth) + assert processed_auth == ('user', 'password') + assert has_secrets is False + + +@pytest.mark.skipif(not secret_type_supported, reason="Running on pre-7.4 robot") +def test_check_and_process_secrets_with_secrets(): + secret_password = Secret('mypassword') + auth = ('user', secret_password) + processed_auth, has_secrets = check_and_process_secrets(auth) + assert processed_auth == ('user', 'mypassword') + assert has_secrets is True + assert not isinstance(processed_auth[1], Secret) + + +@pytest.mark.skipif(not secret_type_supported, reason="Running on pre-7.4 robot") +def test_check_and_process_secrets_with_mixed_secrets(): + secret_user = Secret('myuser') + secret_password = Secret('mypassword') + auth = (secret_user, secret_password) + processed_auth, has_secrets = check_and_process_secrets(auth) + assert processed_auth == ('myuser', 'mypassword') + assert has_secrets is True + assert not isinstance(processed_auth[0], Secret) + assert not isinstance(processed_auth[1], Secret) + + +def test_check_and_process_secrets_with_none(): + processed_auth, has_secrets = check_and_process_secrets(None) + assert processed_auth is None + assert has_secrets is False + + +def test_check_and_process_secrets_with_empty_list(): + processed_auth, has_secrets = check_and_process_secrets([]) + assert processed_auth == [] + assert has_secrets is False