from CommonServerPython import DemistoException from HPEArubaCentralEventCollector import ( main, Client, NUM_OF_RETRIES, RATE_LIMIT_STATUS_CODE, BACKOFF_FACTOR, validate_authentication_params, mask_secret, ) import pytest import demistomock as demisto from CommonServerPython import date_to_timestamp from freezegun import freeze_time import json VENDOR = "aruba" PRODUCT = "central" DATE_FORMAT = "%Y-%m-%dT%H:%M:%S" BASE_URL = "https://server_url" CLIENT_ID = "test client id" CLIENT_SECRET = "test client secret" USER_NAME = "test username" USER_PASSWORD = "test password" CUSTOMER_ID = "test customer id" TEST_TOKEN = "testaccesstoken" TEST_REFRESH_TOKEN = "testrefreshtoken" FROM_DATE = "2024-09-11T03:21:33" FROM_TIME = int(date_to_timestamp(FROM_DATE, DATE_FORMAT) / 1000) CSRF_TOKEN = "testcsrftoken" SESSION_ID = "testsessionid" AUTH_CODE = "testauthcode" FETCH_DATE = "2024-09-12T03:21:33" FETCH_TIME = int(date_to_timestamp(FETCH_DATE, DATE_FORMAT) / 1000) FETCH_LIMIT = 10 # An Access Token bundle that contains only a refresh token (no usable access token), # so seeding it forces a refresh call. DOWNLOAD_TOKEN_REFRESH_ONLY = json.dumps({"refresh_token": TEST_REFRESH_TOKEN, "token_type": "bearer"}) # A full Access Token bundle that also includes a usable access token. DOWNLOAD_TOKEN_FULL = json.dumps( {"refresh_token": TEST_REFRESH_TOKEN, "access_token": TEST_TOKEN, "expires_in": 7200, "token_type": "bearer"} ) def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) def mock_instance_params(mocker, fetch_networking: bool = False): """ Helper function to mock the instance parameters. Args: mocker: pytest mocker object fetch_networking (bool): Instance fetch_networking parameter """ mocker.patch.object( demisto, "params", return_value={ "url": BASE_URL, "auth_method": "Basic Auth", "credentials": { "identifier": CLIENT_ID, "password": CLIENT_SECRET, }, "user": { "identifier": USER_NAME, "password": USER_PASSWORD, }, "customer_id": { "password": CUSTOMER_ID, }, "fetch_networking_events": fetch_networking, "max_audit_events_per_fetch": FETCH_LIMIT, "max_networking_events_per_fetch": FETCH_LIMIT, "proxy": False, "verify": False, }, ) @freeze_time(FETCH_DATE) @pytest.mark.parametrize("fetch_networking", [True, False]) def test_fetch_events_command(mocker, requests_mock, fetch_networking): """ Given: - Instance params When: - Running fetch-events command Then: - Ensure events are fetched and sent to XSIAM as expected """ audit_response_mock = util_load_json("test_data/mock_audit_response.json") networking_response_mock = util_load_json("test_data/mock_networking_response.json") requests_mock.get( f"{BASE_URL}/auditlogs/v1/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=audit_response_mock ) requests_mock.get( f"{BASE_URL}/monitoring/v2/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=networking_response_mock, ) mocker.patch.object(demisto, "command", return_value="fetch-events") mocker.patch.object( demisto, "getLastRun", return_value={ "last_audit_ts": FROM_TIME, "last_networking_ts": FROM_TIME, }, ) mock_instance_params(mocker, fetch_networking=fetch_networking) mocker.patch( "HPEArubaCentralEventCollector.get_integration_context", return_value={"access_token": TEST_TOKEN, "expiry_time": FETCH_TIME + 1}, ) send_events_to_xsiam_mock = mocker.patch("HPEArubaCentralEventCollector.send_events_to_xsiam", return_value={}) main() audit_response_mock["events"].reverse() expected_events = ( audit_response_mock["events"] if not fetch_networking else (audit_response_mock["events"] + networking_response_mock["events"]) ) send_events_to_xsiam_mock.assert_called_once_with(expected_events, vendor=VENDOR, product=PRODUCT) @freeze_time(FETCH_DATE) def test_get_access_token(mocker, requests_mock): """ Given: - A request to get an access token When: - No valid access token exists in the integration context Then: - Obtain a new access token by following the OAuth2 authorization code grant flow """ mocker.patch.object(demisto, "getIntegrationContext", return_value={}) # Mock login request def match_login_request(request): return request.qs.get("client_id")[0] == CLIENT_ID and request.json() == { "username": USER_NAME, "password": USER_PASSWORD, } requests_mock.post( f"{BASE_URL}/oauth2/authorize/central/api/login", additional_matcher=match_login_request, cookies={"csrftoken": CSRF_TOKEN, "session": SESSION_ID}, ) # Mock auth code request def match_auth_code_request(request): return ( request.qs.get("client_id")[0] == CLIENT_ID and request.qs.get("response_type")[0] == "code" and request.json() == {"customer_id": CUSTOMER_ID} ) requests_mock.post( f"{BASE_URL}/oauth2/authorize/central/api", additional_matcher=match_auth_code_request, request_headers={ "Content-Type": "application/json", "Cookie": f"session={SESSION_ID}", "X-CSRF-TOKEN": CSRF_TOKEN, }, json={"auth_code": AUTH_CODE}, ) # Mock token request def match_token_request(request): return request.json() == { "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "grant_type": "authorization_code", "code": AUTH_CODE, } requests_mock.post( f"{BASE_URL}/oauth2/token", additional_matcher=match_token_request, json={ "refresh_token": TEST_REFRESH_TOKEN, "token_type": "bearer", "access_token": TEST_TOKEN, "expires_in": 7200, }, ) mocked_set_integration_context = mocker.patch("HPEArubaCentralEventCollector.set_integration_context") client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name=USER_NAME, user_password=USER_PASSWORD, customer_id=CUSTOMER_ID, ) assert client.get_access_token() == TEST_TOKEN mocked_set_integration_context.assert_called_once_with( { "access_token": TEST_TOKEN, "expiry_time": FETCH_TIME + 7200, "refresh_token": TEST_REFRESH_TOKEN, } ) @freeze_time(FETCH_DATE) def test_refresh_access_token(mocker, requests_mock): """ Given: - A request to get an access token When: - There is an expired access token in the context Then: - Refresh the access token using the refresh token """ # Return an expired token from the context mocker.patch( "HPEArubaCentralEventCollector.get_integration_context", return_value={ "access_token": TEST_TOKEN, "expiry_time": FETCH_TIME - 1, "refresh_token": TEST_REFRESH_TOKEN, }, ) # Mock refresh request new_token = f"refreshed_{TEST_TOKEN}" new_refresh_token = f"refreshed_{TEST_REFRESH_TOKEN}" def match_refresh_request(request): expected_params = { "client_id": [CLIENT_ID], "client_secret": [CLIENT_SECRET], "grant_type": ["refresh_token"], "refresh_token": [TEST_REFRESH_TOKEN], } return request.qs == expected_params requests_mock.post( f"{BASE_URL}/oauth2/token", additional_matcher=match_refresh_request, json={ "refresh_token": new_refresh_token, "token_type": "bearer", "access_token": new_token, "expires_in": 7200, }, ) mocked_set_integration_context = mocker.patch("HPEArubaCentralEventCollector.set_integration_context") client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name=USER_NAME, user_password=USER_PASSWORD, customer_id=CUSTOMER_ID, ) assert client.get_access_token() == new_token mocked_set_integration_context.assert_called_once_with( { "access_token": new_token, "expiry_time": FETCH_TIME + 7200, "refresh_token": new_refresh_token, } ) @freeze_time(FETCH_DATE) def test_fetch_with_duplicates(mocker, requests_mock): """ Given: - fetch events command When: - Some events with the same starting timestamp were previously fetched Then: - Fetched events are returned without the previously fetched ones """ fetch_networking = True full_audit_response = util_load_json("test_data/mock_audit_response.json") full_networking_response = util_load_json("test_data/mock_networking_response.json") mocker.patch.object(demisto, "command", return_value="fetch-events") mock_instance_params(mocker, fetch_networking=fetch_networking) mocker.patch( "HPEArubaCentralEventCollector.get_integration_context", return_value={"access_token": TEST_TOKEN, "expiry_time": FETCH_TIME + 1}, ) # Mock first fetch to get some of the events first_audit_response = full_audit_response.copy() first_audit_response["events"] = first_audit_response["events"][-2:] first_networking_response = full_networking_response.copy() first_networking_response["events"] = first_networking_response["events"][:2] requests_mock.get( f"{BASE_URL}/auditlogs/v1/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=first_audit_response ) requests_mock.get( f"{BASE_URL}/monitoring/v2/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=first_networking_response, ) mocker.patch.object(demisto, "getLastRun", return_value={}) set_last_run_mock = mocker.patch.object(demisto, "setLastRun") send_events_to_xsiam_mock = mocker.patch("HPEArubaCentralEventCollector.send_events_to_xsiam", return_value={}) main() expected_audit_events = list(reversed(first_audit_response["events"])) expected_networking_events = first_networking_response["events"] send_events_to_xsiam_mock.assert_called_once_with( expected_audit_events + expected_networking_events, vendor=VENDOR, product=PRODUCT ) # Mock next fetch to get all of the events, including the previously fetched requests_mock.get( f"{BASE_URL}/auditlogs/v1/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=full_audit_response ) requests_mock.get( f"{BASE_URL}/monitoring/v2/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=full_networking_response, ) mocker.patch.object(demisto, "getLastRun", return_value=set_last_run_mock.call_args[0][0]) send_events_to_xsiam_mock = mocker.patch("HPEArubaCentralEventCollector.send_events_to_xsiam", return_value={}) main() expected_audit_events = list(reversed(full_audit_response["events"][:-2])) expected_networking_events = full_networking_response["events"][2:] send_events_to_xsiam_mock.assert_called_once_with( expected_audit_events + expected_networking_events, vendor=VENDOR, product=PRODUCT ) @pytest.mark.parametrize("should_fail", [True, False]) def test_aruba_auth_test(mocker, should_fail): """ Given: - aruba-auth-test command When: - executing the 'aruba-auth-test' command Then: - CommnadResults will be returned with an informative message of success or failure if it's a known failure, otherwise an exception will be raised. """ mocker.patch.object(demisto, "command", return_value="aruba-auth-test") mock_instance_params(mocker) if should_fail: mocker.patch( "HPEArubaCentralEventCollector.fetch_events", side_effect=Exception("401 - Unauthorized access, authentication required"), ) else: mocker.patch("HPEArubaCentralEventCollector.fetch_events", return_value=({}, [], [])) if should_fail: return_error_mock = mocker.patch("HPEArubaCentralEventCollector.return_error") main() return_error_mock.assert_called() else: return_results_mock = mocker.patch("HPEArubaCentralEventCollector.return_results") main() assert return_results_mock.call_args[0][0].readable_output == "Authentication was successful." def test_test_module_full_bundle_is_non_mutating(mocker): """Test with a full bundle validates using the bundle's access token and never refreshes (non-mutating).""" from HPEArubaCentralEventCollector import test_module client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle=DOWNLOAD_TOKEN_FULL, verify=False, proxy=False, ) validate = mocker.patch.object(client, "validate_access_token") get_token = mocker.patch.object(client, "get_access_token") assert test_module(client) == "ok" validate.assert_called_once_with(TEST_TOKEN) get_token.assert_not_called() def test_test_module_full_bundle_stale_access_token_raises_helpful_error(mocker): """ Given: - A full Access Token bundle whose access token is expired/stale (validation fails). When: - Pressing the Test button. Then: - The refresh token is NOT rotated (get_access_token is not called), and a DemistoException is raised that hints the token may be stale and can be refreshed via the 'aruba-auth-test' command. """ from HPEArubaCentralEventCollector import test_module client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle=DOWNLOAD_TOKEN_FULL, verify=False, proxy=False, ) validate = mocker.patch.object( client, "validate_access_token", side_effect=DemistoException("Error in API call [401] - Unauthorized") ) get_token = mocker.patch.object(client, "get_access_token") with pytest.raises(DemistoException, match="aruba-auth-test"): test_module(client) validate.assert_called_once_with(TEST_TOKEN) # The refresh token must not be rotated by test-module. get_token.assert_not_called() def test_test_module_refresh_only_bundle_raises_without_refreshing(mocker): """ Given: - A bundle that contains only a refresh token (no usable access token). When: - Pressing the Test button. Then: - The refresh token is NOT rotated (get_access_token and validate_access_token are not called), and a DemistoException directs the user to the 'aruba-auth-test' command, because refreshing here would rotate a token that test-module cannot persist. """ from HPEArubaCentralEventCollector import test_module client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle=DOWNLOAD_TOKEN_REFRESH_ONLY, verify=False, proxy=False, ) get_token = mocker.patch.object(client, "get_access_token") validate = mocker.patch.object(client, "validate_access_token") with pytest.raises(DemistoException, match="aruba-auth-test"): test_module(client) # Nothing that could rotate or use the refresh token should have been called. get_token.assert_not_called() validate.assert_not_called() def test_test_module_userpass_only_raises(): """ Given: - test-module command and a client configured with only Username/Password (no Access Token). When: - Pressing the Test button. Then: - A DemistoException is raised directing the user to the aruba-auth-test command, because a full OAuth login would burn Aruba's 30-minute new-token quota. """ from HPEArubaCentralEventCollector import test_module client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name=USER_NAME, user_password=USER_PASSWORD, customer_id=CUSTOMER_ID, access_token_bundle="", verify=False, proxy=False, ) with pytest.raises(DemistoException, match="aruba-auth-test"): test_module(client) @freeze_time(FETCH_DATE) @pytest.mark.parametrize("fetch_networking, should_push_events", [(True, True), (False, False), (True, False), (False, True)]) def test_get_events_command(mocker, requests_mock, fetch_networking, should_push_events): """ Given: - Instance params and command args When: - Running the get events command Then: - Events are fetched and HR is returned. Events are pushed to XSIAM if should_push_events is true. """ audit_response_mock = util_load_json("test_data/mock_audit_response.json") networking_response_mock = util_load_json("test_data/mock_networking_response.json") requests_mock.get( f"{BASE_URL}/auditlogs/v1/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=audit_response_mock ) requests_mock.get( f"{BASE_URL}/monitoring/v2/events", request_headers={"authorization": f"Bearer {TEST_TOKEN}"}, json=networking_response_mock, ) mocker.patch.object(demisto, "command", return_value="aruba-central-get-events") mock_instance_params(mocker, fetch_networking=fetch_networking) mocker.patch.object( demisto, "args", return_value={ "should_push_events": should_push_events, "limit": FETCH_LIMIT, "from_date": FROM_DATE, }, ) mocker.patch( "HPEArubaCentralEventCollector.get_integration_context", return_value={"access_token": TEST_TOKEN, "expiry_time": FETCH_TIME + 1}, ) send_events_to_xsiam_mock = mocker.patch("HPEArubaCentralEventCollector.send_events_to_xsiam", return_value={}) return_results_mock = mocker.patch("HPEArubaCentralEventCollector.return_results") main() return_results_mock.assert_called() if fetch_networking: assert len(return_results_mock.call_args.args[0]) == 2 else: assert len(return_results_mock.call_args.args[0]) == 1 if should_push_events: audit_response_mock["events"].reverse() expected_events = ( audit_response_mock["events"] if not fetch_networking else (audit_response_mock["events"] + networking_response_mock["events"]) ) send_events_to_xsiam_mock.assert_called_once_with(expected_events, vendor=VENDOR, product=PRODUCT) else: send_events_to_xsiam_mock.assert_not_called() def _make_client() -> Client: """Helper to create a Client instance for http_request tests.""" return Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name=USER_NAME, user_password=USER_PASSWORD, customer_id=CUSTOMER_ID, verify=False, proxy=False, ) def test_validate_access_token_uses_given_token_without_refresh(requests_mock): """validate_access_token calls the API with the given token as-is and does not refresh/rotate it.""" client = _make_client() audit = requests_mock.get(f"{BASE_URL}/auditlogs/v1/events", json={"events": [], "total": 0}) client.validate_access_token("some-access-token") assert audit.call_count == 1 assert audit.last_request.headers["authorization"] == "Bearer some-access-token" @freeze_time(FETCH_DATE) def test_http_request_retry_on_429_then_success(mocker): """ Given: - _http_request succeeds (simulating that the internal urllib3 retry handled a transient 429). When: - Calling client.http_request (which passes retry params to _http_request). Then: - The call succeeds and returns the expected data. - The retry-related params (retries, status_list_to_retry, backoff_factor) are passed to _http_request, proving the retry configuration is wired correctly. """ client = _make_client() mocker.patch.object(client, "get_access_token", return_value=TEST_TOKEN) expected_response = {"events": [{"id": 1}]} mock_http = mocker.patch.object( client, "_http_request", return_value=expected_response, ) result = client.http_request("GET", url_suffix="/test/endpoint", params={"limit": 10}) assert result == expected_response mock_http.assert_called_once_with( method="GET", url_suffix="/test/endpoint", params={"limit": 10}, headers={ "accept": "application/json", "authorization": f"Bearer {TEST_TOKEN}", }, retries=NUM_OF_RETRIES, status_list_to_retry=[RATE_LIMIT_STATUS_CODE], backoff_factor=BACKOFF_FACTOR, ) @freeze_time(FETCH_DATE) def test_http_request_repeated_429_raises_demisto_exception(mocker): """ Given: - _http_request always raises a DemistoException wrapping a 429 (rate-limit) response, exceeding NUM_OF_RETRIES attempts. When: - Calling client.http_request. Then: - A DemistoException is raised and is NOT absorbed by the "access token is invalid" branch. """ client = _make_client() mocker.patch.object(client, "get_access_token", return_value=TEST_TOKEN) mocker.patch.object( client, "_http_request", side_effect=DemistoException("Rate limit exceeded", res=mocker.MagicMock(status_code=429)), ) with pytest.raises(DemistoException, match="Rate limit exceeded"): client.http_request("GET", url_suffix="/test/endpoint") @freeze_time(FETCH_DATE) def test_http_request_passes_retry_params_on_initial_and_refresh(mocker): """ Given: - The first _http_request call raises a DemistoException containing "access token is invalid" (triggering the token-refresh branch). - The second _http_request call (after token refresh) succeeds. When: - Calling client.http_request. Then: - Both the initial and the post-refresh _http_request calls include retries=NUM_OF_RETRIES, status_list_to_retry=[RATE_LIMIT_STATUS_CODE], and backoff_factor=BACKOFF_FACTOR. """ import copy client = _make_client() refreshed_token = "refreshed_token" mocker.patch.object(client, "get_access_token", side_effect=[TEST_TOKEN, refreshed_token]) expected_response = {"events": [{"id": 2}]} # Capture deep copies of kwargs at call time, because http_request mutates the # headers dict in-place when refreshing the token, which would make both # call_args_list entries point to the same (mutated) dict object. captured_kwargs: list[dict] = [] original_side_effects = iter( [ DemistoException("access token is invalid"), expected_response, ] ) def capture_and_delegate(**kwargs): captured_kwargs.append(copy.deepcopy(kwargs)) result = next(original_side_effects) if isinstance(result, Exception): raise result return result mocker.patch.object(client, "_http_request", side_effect=capture_and_delegate) result = client.http_request("GET", url_suffix="/test/endpoint", params={"key": "val"}) assert result == expected_response assert len(captured_kwargs) == 2 expected_retry_kwargs = { "retries": NUM_OF_RETRIES, "status_list_to_retry": [RATE_LIMIT_STATUS_CODE], "backoff_factor": BACKOFF_FACTOR, } # Verify initial request (with original token) includes retry params first_kwargs = captured_kwargs[0] assert first_kwargs["headers"]["authorization"] == f"Bearer {TEST_TOKEN}" for key, value in expected_retry_kwargs.items(): assert first_kwargs[key] == value, f"Initial request missing or wrong retry param '{key}': {first_kwargs.get(key)}" # Verify post-refresh retry request (with refreshed token) includes retry params second_kwargs = captured_kwargs[1] assert second_kwargs["headers"]["authorization"] == f"Bearer {refreshed_token}" for key, value in expected_retry_kwargs.items(): assert second_kwargs[key] == value, f"Retry request missing or wrong retry param '{key}': {second_kwargs.get(key)}" @freeze_time(FETCH_DATE) def test_get_access_token_seeds_from_access_token_bundle(mocker, requests_mock): """ Given: - An instance configured with a pasted Access Token JSON (refresh token only) and no username/password. When: - get_access_token is called with an empty integration context. Then: - The refresh token from the bundle is used to obtain an access token, and the context is seeded with 'seeded_token' so it is not re-seeded on subsequent runs. """ mocker.patch("HPEArubaCentralEventCollector.get_integration_context", return_value={}) new_token = f"seeded_{TEST_TOKEN}" new_refresh_token = f"rotated_{TEST_REFRESH_TOKEN}" def match_refresh_request(request): return request.qs.get("refresh_token") == [TEST_REFRESH_TOKEN] requests_mock.post( f"{BASE_URL}/oauth2/token", additional_matcher=match_refresh_request, json={ "refresh_token": new_refresh_token, "token_type": "bearer", "access_token": new_token, "expires_in": 7200, }, ) mocked_set_integration_context = mocker.patch("HPEArubaCentralEventCollector.set_integration_context") client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle=DOWNLOAD_TOKEN_REFRESH_ONLY, ) assert client.get_access_token() == new_token # The context is persisted twice: once when seeding, and again after the refresh. mocked_set_integration_context.assert_called_with( { "seeded_token": DOWNLOAD_TOKEN_REFRESH_ONLY, "access_token": new_token, "expiry_time": FETCH_TIME + 7200, "refresh_token": new_refresh_token, } ) @freeze_time(FETCH_DATE) def test_get_access_token_no_credentials_and_no_token_raises(mocker): """ Given: - An instance with no cached/refresh token, no downloaded token, and no username/password. When: - get_access_token is called. Then: - A DemistoException is raised instead of attempting a doomed login (prevents the 401/429 loop). """ mocker.patch("HPEArubaCentralEventCollector.get_integration_context", return_value={}) client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle="", ) with pytest.raises(DemistoException, match="Unable to authenticate"): client.get_access_token() def test_validate_authentication_params_download_token_missing_token(): """ Given: - Authentication Method is "Access Token" but no Access Token is provided. When: - validate_authentication_params is called. Then: - A DemistoException is raised. """ with pytest.raises(DemistoException, match="no Access Token was provided"): validate_authentication_params("Access Token", "", "", "", "") def test_validate_authentication_params_userpass_missing_user(): """ Given: - Authentication Method is "Basic Auth" but Username/Password are missing. When: - validate_authentication_params is called. Then: - A DemistoException is raised. """ with pytest.raises(DemistoException, match="Username and/or Password is missing"): validate_authentication_params("Basic Auth", "", "", "", CUSTOMER_ID) def test_validate_authentication_params_userpass_missing_customer_id(): """ Given: - Authentication Method is "Basic Auth", Username/Password provided, but no Customer ID. When: - validate_authentication_params is called. Then: - A DemistoException is raised. """ with pytest.raises(DemistoException, match="Customer ID is missing"): validate_authentication_params("Basic Auth", "", USER_NAME, USER_PASSWORD, "") def test_validate_authentication_params_valid_configs(): """ Given: - Valid configurations: Access Token method with a token, or Basic Auth method with a Customer ID. When: - validate_authentication_params is called. Then: - No exception is raised. """ # Access Token method: only the token is needed. validate_authentication_params("Access Token", DOWNLOAD_TOKEN_REFRESH_ONLY, "", "", "") # Basic Auth method with a Customer ID. validate_authentication_params("Basic Auth", "", USER_NAME, USER_PASSWORD, CUSTOMER_ID) def test_parse_download_token_full_json(): """ Given: - A full Access Token JSON bundle (as downloaded from the Aruba Central UI). When: - Client.parse_download_token is called. Then: - The refresh_token, access_token, and expires_in are extracted. """ bundle = { "access_token": TEST_TOKEN, "refresh_token": TEST_REFRESH_TOKEN, "expires_in": 7200, "token_type": "bearer", "scope": "all", } refresh_token, access_token, expires_in = Client.parse_download_token(json.dumps(bundle)) assert refresh_token == TEST_REFRESH_TOKEN assert access_token == TEST_TOKEN assert expires_in == 7200 def test_parse_download_token_bare_string_rejected(): """ Given: - A bare refresh-token string instead of the full Access Token JSON. When: - Client.parse_download_token is called. Then: - A DemistoException is raised (the full JSON bundle is required). """ with pytest.raises(DemistoException, match="not valid JSON"): Client.parse_download_token(TEST_REFRESH_TOKEN) def test_parse_download_token_invalid_json(): """ Given: - A value that looks like JSON but is malformed. When: - Client.parse_download_token is called. Then: - A DemistoException is raised. """ with pytest.raises(DemistoException, match="not valid JSON"): Client.parse_download_token('{"refresh_token": ') def test_parse_download_token_json_missing_refresh_token(): """ Given: - A JSON bundle without a refresh_token field. When: - Client.parse_download_token is called. Then: - A DemistoException is raised. """ with pytest.raises(DemistoException, match="missing the required 'refresh_token' field"): Client.parse_download_token(json.dumps({"access_token": TEST_TOKEN, "expires_in": 7200})) @freeze_time(FETCH_DATE) def test_get_access_token_seeds_access_token_from_json_bundle(mocker): """ Given: - An Access Token pasted as a full JSON bundle that includes a still-valid access_token. When: - get_access_token is called with an empty integration context. Then: - The access_token from the bundle is used directly (no refresh call) and the context is seeded. """ mocker.patch("HPEArubaCentralEventCollector.get_integration_context", return_value={}) mocked_set_integration_context = mocker.patch("HPEArubaCentralEventCollector.set_integration_context") bundle = json.dumps( { "access_token": TEST_TOKEN, "refresh_token": TEST_REFRESH_TOKEN, "expires_in": 7200, "token_type": "bearer", } ) client = Client( base_url=BASE_URL, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, user_name="", user_password="", customer_id="", access_token_bundle=bundle, ) # No requests_mock is set up: if the code tried to refresh, it would fail. It should use the seeded token. assert client.get_access_token() == TEST_TOKEN mocked_set_integration_context.assert_called_once_with( { "seeded_token": bundle, "refresh_token": TEST_REFRESH_TOKEN, "access_token": TEST_TOKEN, "expiry_time": FETCH_TIME + 7200, } ) @pytest.mark.parametrize( "secret, expected", [ (None, ""), # never set ("", ""), # set but empty (distinct from None) ("short", "***(5)"), ("exactlytwelve", "exac…elve(13)"), # len 13 > 3*4 -> prefix + suffix shown ("abcdefghijkl", "***(12)"), # len 12 == 3*4 -> fully masked ("paf8bUQEpxcjxeFYfIlTiiO7fDvuZy4R", "paf8…Zy4R(32)"), # first 4 + last 4 + length ], ) def test_mask_secret(secret, expected): """ Given: secrets in every state (None, empty, short, and long). When: mask_secret is called. Then: None, empty, and short each produce a distinct, non-revealing marker, and long values expose only a short prefix+suffix plus length. """ result = mask_secret(secret) # The full secret must never appear in the masked output. if secret: assert secret not in result assert result == expected