Workday Sign On Event Collector
Use the Workday Sign On Event Collector integration to get sign on logs from Workday.
Analytics & SIEM · Workday
Details
| ID | Workday Sign On Event Collector |
|---|---|
| Provider | Workday |
| Category | Analytics & SIEM |
| From Version | 8.2.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM Cloud Posture Security |
README
Use the Workday Sign On Event Collector integration to get sign on logs from Workday.
This integration was integrated and tested with version v37.0 of Workday Sign On Event Collector.
Configure Workday Sign On Event Collector in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://services1.myworkday.com) | API Endpoint of Workday server. Can be obtained from View API Clients report in Workday application. | True |
| Tenant Name | The name of the Workday Tenant. Can be obtained from View API Clients report in Workday application. | True |
| API Version | Some tenants may be using different API versions. Check in your Workday tenant to determine the version to use. Default is v40.0. | False |
| Username | True | |
| Password | True | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Max events per fetch | The maximum number of sign on events to retrieve. Large amount of events may cause performance issues. | False |
| Events Fetch Interval | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
workday-get-sign-on-events
Returns sign on events extracted from Workday. This command is used for developing/debugging and is to be used with caution, as it can create events, leading to events duplication and exceeding the API request limitation.
Base Command
workday-get-sign-on-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | Set this argument to True in order to create events, otherwise the command will only display them. Possible values are: True, False. Default is False. | Required |
| limit | The maximum number of events to return. Default is 1000. | Optional |
| from_date | The date and time of the earliest event. The default timezone is UTC/GMT. The time format is “{yyyy}-{mm}-{dd}T{hh}:{mm}:{ss}Z”. Example: “2021-05-18T13:45:14Z” indicates May 18, 2021, 1:45PM UTC. | Optional |
| to_date | The time format is “{yyyy}-{mm}-{dd}T{hh}:{mm}:{ss}Z”. Example: “2021-05-18T13:45:14Z” indicates May 18, 2021, 1:45PM UTC. | Optional |
| relative_from_date | The query from date, for example, “5 minutes”. Be advised, it is strongly suggested to keep this parameter limited in time. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
base_url— Server URL (e.g., https://services1.myworkday.com) (required)tenant_name— Tenant Name (required)api_version— API Versioncredentials— Username (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsmax_fetch— Max events per fetcheventFetchInterval— Events Fetch Interval
Commands (1)
-
workday-get-sign-on-eventsReturns sign on events extracted from Workday. This command is used for developing/debugging and is to be used with caution, as it can create events, leading to events duplication and exceeding the API request limitation.
import json import unittest from typing import Any from unittest.mock import patch import pytest from CommonServerPython import DemistoException from freezegun import freeze_time from WorkdaySignOnEventCollector import ( Client, convert_to_json, fetch_sign_on_events_command, fetch_sign_on_logs, fletcher16, generate_pseudo_id, get_from_time, get_sign_on_events_command, main, VENDOR, PRODUCT, get_api_version, process_and_filter_events, ) def test_get_from_time() -> None: """ Given: - A time duration in seconds (3600 seconds or 1 hour ago). When: - The function `get_from_time` is called to convert this duration to a UTC datetime string. Then: - Ensure that the returned value is a string. - Validate that the string ends with 'Z', indicating it's in UTC format. """ # Given: A time duration of 3600 seconds (or 1 hour) ago. seconds_ago = 3600 # 1 hour ago # When: Calling the function to convert this to a UTC datetime string. result: Any = get_from_time(seconds_ago) # Then: Validate the type and format of the returned value. assert isinstance(result, str) assert result.endswith("Z") # Check if it's in the right format def test_fletcher16() -> None: """ Given: - Two types of byte strings, one containing the word 'test' and another being empty. When: - The function `fletcher16` is called to calculate the checksum for these byte strings. Then: - Ensure that the checksum calculated for the byte string 'test' matches the expected value of 22976. - Validate that the checksum for an empty byte string is 0. """ # Given: A byte string containing the word 'test'. data = b"test" # When: Calling `fletcher16` to calculate the checksum. result: Any = fletcher16(data) # Then: Validate that the checksum matches the expected value. expected = 22976 assert result == expected # Given: An empty byte string. data = b"" # When: Calling `fletcher16` to calculate the checksum. result = fletcher16(data) # Then: Validate that the checksum for an empty byte string is 0. expected = 0 assert result == expected def test_generate_pseudo_id() -> None: """ Given: - Four different event dictionaries: 1. A valid event dictionary with known values. 2. An empty event dictionary. 3. An event dictionary missing the "Signon_DateTime" key. 4. A large event dictionary. When: - Calling `generate_pseudo_id` to calculate a unique ID based on the event dictionary. Then: - For the first case, ensure that the unique ID matches the expected value. - For the second and third cases, ensure that an exception is raised. - For the fourth case, ensure the function can handle large dictionaries without errors. """ # Given: A valid event dictionary with known values. event1 = { "Short_Session_ID": "12345", "User_Name": "ABC123", "Successful": 1, "Signon_DateTime": "2023-09-04T07:47:57.460-07:00", } # When: Calling `generate_pseudo_id` to calculate the unique ID. event1_str: str = json.dumps(event1, sort_keys=True) expected_checksum1: Any = fletcher16(event1_str.encode()) expected_unique_id1: str = f"{expected_checksum1}_{event1['Signon_DateTime']}" result1: str = generate_pseudo_id(event1) # Then: Validate that the unique ID matches the expected value. assert result1 == expected_unique_id1 # Given: An empty event dictionary. event2 = {} # When & Then: Calling `generate_pseudo_id` and expecting an exception. try: generate_pseudo_id(event2) except DemistoException as e: assert ( str(e) == "While calculating the pseudo ID for an event, an event without a Signon_DateTime was " "found.\nError: 'Signon_DateTime'" ) else: raise AssertionError("Expected DemistoException but did not get one") # Given: An event dictionary missing the "Signon_DateTime" key. event3 = { "Short_Session_ID": "12345", "User_Name": "ABC123", "Successful": 1, } # When & Then: Calling `generate_pseudo_id` and expecting an exception. try: generate_pseudo_id(event3) except DemistoException: pass else: raise AssertionError("Expected DemistoException but did not get one") # Given: A large event dictionary. event4 = {str(i): i for i in range(10000)} # Create a large dictionary event4["Signon_DateTime"] = "2023-09-04T07:47:57.460-07:00" # Add a Signon_DateTime key # When & Then: Calling `generate_pseudo_id` to check if the function can handle it. assert generate_pseudo_id(event4) def test_process_and_filter_events() -> None: """ Given: - A list of two valid sign-on events that differ by 1 second in their "Signon_DateTime". - An initial time ("from_time") that matches the "Signon_DateTime" of one of the events. - An empty set of pseudo_ids from the previous run. When: - Calling the `process_and_filter_events` function to filter out duplicates and process events for the next iteration. Then: - The list of non-duplicate events should match the original list of events. - The set of pseudo_ids for the next iteration should contain two elements. - Each event in the list of non-duplicates should have an additional "_time" key that matches its "Signon_DateTime". """ # Given: A list of two valid sign-on events and other initial conditions events = [ { "Short_Session_ID": "12345", "User_Name": "ABC6789", "Successful": 1, "Signon_DateTime": "2023-09-04T07:47:57.460-07:00", }, { "Short_Session_ID": "12346", "User_Name": "ABC6790", "Successful": 1, "Signon_DateTime": "2023-09-04T07:47:57.460-07:00", }, ] from_time: str = "2021-09-01T12:00:00Z" previous_run_pseudo_ids: set[Any] = set() # Assume no previous checksums for simplicity # When: Calling the function to test non_duplicates, pseudo_ids_for_next_iteration = process_and_filter_events(events, from_time, previous_run_pseudo_ids) # Then: Validate the function's output assert non_duplicates == events # Check if the list of non-duplicates is as expected assert len(pseudo_ids_for_next_iteration) == 2 # Check if the set of pseudo_ids for next iteration is updated # Check if '_time' key is added to each event for event in non_duplicates: assert "_time" in event assert event["_time"] == event["Signon_DateTime"] def test_convert_to_json() -> None: """ Given: - A sample XML response string containing a single 'Workday_Account_Signon' entry with a 'Signon_DateTime'. When: - Calling the 'convert_to_json' function to convert the XML data to a Python dictionary. Then: - The function should return two Python dictionaries. - The first dictionary should represent the entire XML structure. - The second dictionary should contain just the 'Workday_Account_Signon' entries. - Both dictionaries should correctly reflect the 'Signon_DateTime' from the original XML. """ # Given: Test with XML data (this is a simplified version for the sake of the test) xml_response = """ <Envelope> <Body> <Get_Workday_Account_Signons_Response> <Response_Data> <Workday_Account_Signon> <Signon_DateTime>2023-09-04T07:47:57.460-07:00</Signon_DateTime> </Workday_Account_Signon> </Response_Data> </Get_Workday_Account_Signons_Response> </Body> </Envelope> """ # When: Calling the function to test raw_json_response, account_signon_data = convert_to_json(xml_response) # Then: Check if the converted data matches the expected structure assert ( raw_json_response["Envelope"]["Body"]["Get_Workday_Account_Signons_Response"]["Response_Data"]["Workday_Account_Signon"][ 0 ]["Signon_DateTime"] == "2023-09-04T07:47:57.460-07:00" ) assert account_signon_data["Workday_Account_Signon"][0]["Signon_DateTime"] == "2023-09-04T07:47:57.460-07:00" def test_generate_workday_account_signons_body() -> None: """ Given: - A Client object initialized with a base URL, verification settings, a tenant name, and login credentials. - Parameters specifying the page, count, and time range for fetching Workday sign-on events. When: - Calling the 'generate_workday_account_signons_body' method on the Client object to generate the SOAP request body. Then: - The returned SOAP request body should contain all the specified parameters. - The body should also contain the username and password for authentication. """ # Given: Initialize a Client object with sample data mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "test_user", "password": "test_pass"}, "insecure": True, } client = Client(params=mock_params) # When: Generate the SOAP request body body = client.generate_workday_account_signons_body( page=1, count=10, to_time="2021-09-01T12:00:00Z", from_time="2021-09-01T11:00:00Z", ) # Then: Verify that the SOAP request body contains all the specified parameters assert "<bsvc:Page>1</bsvc:Page>" in body assert "<bsvc:Count>10</bsvc:Count>" in body assert "<bsvc:From_DateTime>2021-09-01T11:00:00Z</bsvc:From_DateTime>" in body assert "<bsvc:To_DateTime>2021-09-01T12:00:00Z</bsvc:To_DateTime>" in body assert "<wsse:Username>test_user</wsse:Username>" in body assert ( '<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test_pass</wsse:Password>' # noqa:E501 in body ) def test_generate_test_payload() -> None: """ Given: - A Client object initialized with a base URL, verification settings, a tenant name, and login credentials. - Parameters specifying the time range for fetching Workday sign-on events for the test payload. When: - Calling the 'generate_test_payload' method on the Client object to generate a SOAP request payload for testing. Then: - The returned SOAP request payload should contain all the specified parameters. - The payload should also contain the username and password for authentication. """ # Given: Initialize a Client object with sample data mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "test_user", "password": "test_pass"}, "insecure": True, } client = Client(params=mock_params) # When: Generate the SOAP request payload for testing payload = client.generate_test_payload(from_time="2021-09-01T11:00:00Z", to_time="2021-09-01T12:00:00Z") # Then: Verify that the SOAP request payload contains all the specified parameters assert "<bsvc:Page>1</bsvc:Page>" in payload assert "<bsvc:Count>1</bsvc:Count>" in payload assert "<bsvc:From_DateTime>2021-09-01T11:00:00Z</bsvc:From_DateTime>" in payload assert "<bsvc:To_DateTime>2021-09-01T12:00:00Z</bsvc:To_DateTime>" in payload assert "<wsse:Username>test_user</wsse:Username>" in payload assert ( '<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test_pass</wsse:Password>' # noqa:E501 in payload ) @pytest.mark.parametrize( "api_version, from_time, to_time, expected_version", [ # Given valid api_version and time range ("v42.1", "2021-09-01T11:00:00Z", "2021-09-01T12:00:00Z", "v42.1"), # Given default api_version and time range ("v40.0", "2021-09-01T11:00:00Z", "2021-09-01T12:00:00Z", "v40.0"), # Given valid api_version and different time range ("v50.0", "2021-10-01T11:00:00Z", "2021-10-01T12:00:00Z", "v50.0"), # Given invalid api_version should fallback to default ("invalid", "2021-09-01T11:00:00Z", "2021-09-01T12:00:00Z", "v40.0"), ], ) def test_generate_test_payload_with_version(api_version, from_time, to_time, expected_version) -> None: """ Given: - A Client object initialized with a base URL, verification settings, a tenant name, login credentials, and an API version - Parameters specifying the time range for fetching Workday sign-on events for the test payload. When: - Calling the 'generate_test_payload' method on the Client object to generate a SOAP request payload for testing. Then: - The returned SOAP request payload should contain all the specified parameters, including the correct API version. - The payload should also contain the username and password for authentication. """ # Given: Initialize a Client object with sample data mock_params = { "base_url": "https://something.test", # disable-secrets-detection "verify_certificate": True, "proxy": False, "tenant_name": "test_tenant", "credentials": { "identifier": "test_user", "password": "test_pass", }, "api_version": api_version, } client = Client(params=mock_params) # When: Generate the SOAP request payload for testing payload = client.generate_test_payload(from_time=from_time, to_time=to_time) # Then: Verify that the SOAP request payload contains all the specified parameters assert "<bsvc:Page>1</bsvc:Page>" in payload assert "<bsvc:Count>1</bsvc:Count>" in payload assert f"<bsvc:From_DateTime>{from_time}</bsvc:From_DateTime>" in payload assert f"<bsvc:To_DateTime>{to_time}</bsvc:To_DateTime>" in payload assert "<wsse:Username>test_user</wsse:Username>" in payload assert ( '<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test_pass</wsse:Password>' # noqa:E501 in payload ) # Verify that the SOAP request payload contains the correct API version assert ( f'<bsvc:Get_Workday_Account_Signons_Request xmlns:bsvc="urn:com.workday/bsvc" bsvc:version="{expected_version}">' in payload ) def test_convert_to_json_valid_input() -> None: """ Given: - An XML-formatted response string from the Workday API, containing sign-on event data. When: - Calling the 'convert_to_json' function to convert the XML response to JSON format. Then: - The function should return two JSON objects: one containing the full JSON-converted data, and another containing only the sign-on event data. - Both JSON objects should be properly formatted and contain the expected data fields. """ # Given: An XML-formatted response string from the Workday API response = """ <Envelope> <Body> <Get_Workday_Account_Signons_Response> <Response_Data> <Workday_Account_Signon> <Signon_DateTime>2021-09-01T11:00:00Z</Signon_DateTime> </Workday_Account_Signon> </Response_Data> </Get_Workday_Account_Signons_Response> </Body> </Envelope> """ # When: Converting the XML to JSON full_json, account_signon_data = convert_to_json(response) # Then: Validate the full_json data structure envelope = full_json.get("Envelope", {}) body = envelope.get("Body", {}) response = body.get("Get_Workday_Account_Signons_Response", {}) response_data = response.get("Response_Data", {}) workday_account_signons = response_data.get("Workday_Account_Signon", []) # Assertions for full_json assert isinstance(workday_account_signons, list), "workday_account_signons is not a list" assert workday_account_signons, "workday_account_signons is empty" assert workday_account_signons[0].get("Signon_DateTime") == "2021-09-01T11:00:00Z" # Then: Validate the account_signon_data structure workday_account_signons_data = account_signon_data.get("Workday_Account_Signon", []) # Assertions for account_signon_data assert workday_account_signons_data assert workday_account_signons_data[0].get("Signon_DateTime") == "2021-09-01T11:00:00Z" class TestFetchSignOnLogs(unittest.TestCase): def setUp(self) -> None: """ Given: - A Client object with mock URL, tenant, username, and password. When: - Setting up each unit test case. Then: - The Client object should be initialized and ready for testing. """ mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "mock_user", "password": "mock_pass"}, "insecure": True, } self.client = Client(params=mock_params) @patch.object(Client, "retrieve_events") def test_fetch_sign_on_logs_single_page(self, mock_retrieve_events) -> None: """ Given: - A mock Client object with a retrieve_events method that returns a sample response. - The sample response contains a single Workday sign-on event. When: - Calling the fetch_sign_on_logs function to fetch sign-on logs. Then: - The function should return a list of events. - The length of the list should be 1. - The event in the list should have the User_Name "John". """ # Given: Sample data to be returned by the mock mock_response = ( { "Workday_Account_Signon": [ { "Signon_DateTime": "2021-09-01T11:00:00Z", "User_Name": "John", "Short_Session_ID": "123456", "Successful": 1, } ] }, 1, ) # Setup: Configure the mock to return the sample data mock_retrieve_events.return_value = mock_response # When: Fetching sign-on logs events = fetch_sign_on_logs(self.client, 10, "2021-09-01T00:00:00Z", "2021-09-02T00:00:00Z") # Then: Validate the function's return value assert len(events) == 1 assert events[0]["User_Name"] == "John" class TestGetSignOnEventsCommand(unittest.TestCase): def test_get_sign_on_events_command(self) -> None: """ Given: - A Client object with mock settings. - A patch for the fetch_sign_on_logs function to return a mock event. - The mock event has details such as Signon_DateTime, User_Name, Short_Session_ID, and Successful status. When: - Calling the get_sign_on_events_command function to get sign-on events between two date-time ranges. Then: - The function should return a list of events and results. - The length of the list should be 1. - The event in the list should have the User_Name "John" and _time "2021-09-01T11:00:00Z". - The readable_output of the results should start with "### Sign On Events List:". """ # Given: Sample data to be returned by the mock mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "mock_user", "password": "mock_pass"}, "insecure": True, } mock_events = [ { "Signon_DateTime": "2023-09-04T07:47:57.460-07:00", "User_Name": "John", "Short_Session_ID": "123456", "Successful": 1, "_time": "2021-09-01T11:00:00Z", # This is added by the process_events function } ] # Setup: Use patch to mock the fetch_sign_on_logs function with patch("WorkdaySignOnEventCollector.fetch_sign_on_logs", return_value=mock_events): client = Client(params=mock_params) # When: Calling the get_sign_on_events_command events, results = get_sign_on_events_command(client, "2021-09-01T00:00:00Z", "2021-09-02T00:00:00Z", 10) # Then: Validate the function's return value assert len(events) == 1 assert events[0]["User_Name"] == "John" assert events[0]["_time"] == "2023-09-04T07:47:57.460-07:00" assert results.readable_output.startswith("### Sign On Events List:") @freeze_time("2023-09-04T00:00:00.000-07:00") def test_fetch_sign_on_events_command_single_page() -> None: """ Given: - A Client object with mock settings. - A patch for the Client's retrieve_events method to return a mock event. - A patch for demisto.getLastRun function to return a mock last_run dictionary. - The mock event has details such as Signon_DateTime, User_Name, Short_Session_ID, and Successful status. - The mock last_run dictionary contains last_fetch_time and previous_run_pseudo_ids. When: - Calling the fetch_sign_on_events_command function to fetch sign-on events. Then: - The function should return a list of events and a new_last_run dictionary. - The length of the list should be 1. - The event in the list should have the User_Name "John" and _time "2021-09-01T11:00:00Z". - The new_last_run dictionary should have last_fetch_time updated to "2021-09-01T11:00:00Z". """ # Given: Sample data to be returned by the mock mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "mock_user", "password": "mock_pass"}, "insecure": True, } mock_events = [ { "Signon_DateTime": "2023-09-04T07:47:57.460-07:00", "User_Name": "John", "Short_Session_ID": "123456", "Successful": 1, "_time": "2023-09-04T07:47:57.460-07:00", # This is added by the process_events function } ] # Setup: Mock the client's retrieve_events method and demisto.getLastRun function mock_retrieve_response = ({"Workday_Account_Signon": mock_events}, 1) mock_last_run = { "last_fetch_time": "2023-09-04T07:47:57.460-07:00", "previous_run_pseudo_ids": set(), } # When: Calling the fetch_sign_on_events_command with ( patch.object(Client, "retrieve_events", return_value=mock_retrieve_response), patch("demistomock.getLastRun", return_value=mock_last_run), ): client = Client(params=mock_params) events, new_last_run = fetch_sign_on_events_command(client, 10, mock_last_run) # Then: Validate the function's return value assert len(events) == 1 assert events[0]["User_Name"] == "John" assert events[0]["_time"] == "2023-09-04T07:47:57.460-07:00" assert new_last_run["last_fetch_time"] == "2023-09-04T07:47:57.460-07:00" def test_main_fetch_events() -> None: """ Given: - A set of mock parameters for the client. - Mock functions for demisto's getLastRun, setLastRun, and params. - Mock for the fetch_sign_on_events_command function to return mock events and new last_run data. - Mock for the send_events_to_xsiam function. When: - The main function is called and the command is 'fetch-events'. Then: - Ensure that fetch_sign_on_events_command is called with the correct arguments. - Ensure that send_events_to_xsiam is called with the mock events. - Ensure that setLastRun is called to update the last_run data. """ # Given: Mock parameters and last run data mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": "TestUser", "password": "testpass"}, "insecure": True, } # Mocking demisto.command to return 'fetch-events' with ( patch("demistomock.command", return_value="fetch-events"), patch("demistomock.getLastRun", return_value={"some": "data"}), patch("demistomock.setLastRun") as mock_set_last_run, patch("demistomock.params", return_value=mock_params), patch("WorkdaySignOnEventCollector.Client") as mock_client, patch("WorkdaySignOnEventCollector.fetch_sign_on_events_command") as mock_fetch_sign_on_events_command, patch("WorkdaySignOnEventCollector.send_events_to_xsiam") as mock_send_events_to_xsiam, ): # Mocking the output of fetch_sign_on_events_command mock_events = [{"event": "data"}] mock_new_last_run = {"new": "data"} mock_fetch_sign_on_events_command.return_value = ( mock_events, mock_new_last_run, ) # When: Calling the main function main() # Then: Validate the function calls and arguments mock_fetch_sign_on_events_command.assert_called_with( client=mock_client.return_value, max_fetch=10000, last_run={"some": "data"}, ) mock_send_events_to_xsiam.assert_called_with(mock_events, vendor=VENDOR, product=PRODUCT) mock_set_last_run.assert_called_with(mock_new_last_run) @pytest.mark.parametrize( "username, escaped_username, password, escaped_password", [ ("username&", "username&", "pass&", "pass&"), ("username>", "username>", "pass>", "pass>"), ("username<", "username<", "pass<", "pass<"), ("username", "username", "pass", "pass"), ], ) def test_escaping_user_name(username, escaped_username, password, escaped_password): """ Given: A Client object initialized with a base URL, verification settings, a tenant name, and login credentials. In the first 3 cases the credentials contains a special character that needs to be escaped, and the last case checks that in a case of a credentials without special characters, they don't change. When: Creating a new Workday Sign Ons client. Then: Check that the credentials are escaped correctly. """ mock_params = { "tenant_name": "TestTenant", "max_fetch": "10000", "base_url": "https://testurl.com", "credentials": {"identifier": username, "password": password}, "insecure": True, } client = Client(params=mock_params) assert client.username == escaped_username assert client.password == escaped_password @pytest.mark.parametrize( "params, default, expected", [ # Given valid api_version, when get_api_version is called, then it returns the valid api_version ({"api_version": "v42.1"}, "v40.0", "v42.1"), # Given invalid api_version, when get_api_version is called, then it returns the default value ({"api_version": "42.1"}, "v40.0", "v40.0"), # Given missing api_version, when get_api_version is called, then it returns the default value ({}, "v40.0", "v40.0"), # Given invalid api_version with custom default, when get_api_version is called, then it returns the custom default value ({"api_version": "invalid_format"}, "v50.0", "v50.0"), # Given another valid api_version, when get_api_version is called, then it returns the valid api_version ({"api_version": "v50.0"}, "v40.0", "v50.0"), ], ) def test_get_api_version(params, default, expected): """ Test get_api_version with various inputs using parameterization. Given: Different sets of params and default values. When: get_api_version is called with these params. Then: It should return the expected result. """ # When result = get_api_version(params, default) # Then assert result == expected