import json from pathlib import Path from typing import Any, cast from urllib.parse import quote import demistomock as demisto import pytest from CommonServerPython import DemistoException, assign_params from Infoblox import ( INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY, INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY, INTEGRATION_COMMON_NAME_CONTEXT_KEY, INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY, INTEGRATION_COMMON_RAW_RESULT_EXTENSION_ATTRIBUTES_KEY, INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY, INTEGRATION_COMMON_REFERENCE_ID_CONTEXT_KEY, INTEGRATION_CONTEXT_NAME, INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY, INTEGRATION_HOST_RECORDS_CONTEXT_NAME, INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY, INTEGRATION_IP_CONTEXT_NAME, INTEGRATION_MAX_RESULTS_DEFAULT, INTEGRATION_NETWORK_INFO_CONTEXT_KEY, IP_MAPPING, InfoBloxNIOSClient, IPv4AddressStatus, get_extended_attributes_context, get_host_records_command, get_ip_command, get_network_info_command, transform_ext_attrs, transform_host_records_context, transform_ip_context, transform_ipv4_range, transform_network_info_context, ) BASE_URL = "https://example.com/v1/" POST_NEW_ZONE_RESPONSE = { "result": { "_ref": "zone_rp/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS50ZXN0:test.com/default", "disable": False, "fqdn": "test.com", "rpz_policy": "GIVEN", "rpz_severity": "WARNING", "rpz_type": "LOCAL", "view": "default", } } API_ERROR_OBJ = { "Error": "AdmConDataError: None (IBDataConflictError: IB.Data.Conflict:Duplicate object 'test123.com' of type zone " "exists in the database.)", "code": "Client.Ibap.Data.Conflict", "text": "Duplicate object 'test123.com' of type zone exists in the database.", } # disable-secrets-detection-start SSL_ERROR = ( 'Failed to parse json object from response: b\'\r\n\r\n\r\n\r\n\r\nCertificate Error\r\n\r\n\r\n\r\n
\r\n

Certificate Error

\r\n

There is an issue with ' "the SSL certificate of the server you are trying to contact.

\r\n

Certificate Name: " "www.infoblox.com

\r\n

IP:

\r\n

Category: any

\r\n

Issuer: " "www.infoblox.com

\r\n

Status: expired

\r\n

Reason:

\r\n

User: " "

\r\n
\r\n\r\n\r\n\r\n'" ) # disable-secrets-detection-end GET_USER_LIST = { "account": [ {"username": "User1", "name": "DBot Demisto", "isLocked": False}, {"username": "User2", "name": "Demisto DBot", "isLocked": True}, ] } REQUEST_PARAM_ZONE = ( f"?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&_return_fields%2B=fqdn%2Crpz_policy%2Crpz_severity%2Crpz_type%2C" "substitute_name%2Ccomment%2Cdisable" ) # noqa: E501 client = InfoBloxNIOSClient("https://example.com/v1/") TEST_PATH = Path(__file__).parent / "test_data" def util_load_json(file_name: str): """Load file in JSON format.""" file_path = TEST_PATH / file_name with open(file_path, encoding="utf-8") as f: return json.loads(f.read()) def util_load_text_data(file_name: str) -> str: """Load a text file.""" file_path = TEST_PATH / file_name with open(file_path, encoding="utf-8") as f: return f.read() class TestHelperFunctions: def test_parse_demisto_exception_unauthorized_error(self): from Infoblox import parse_demisto_exception json_err = "Expecting value: line 1 column 1 (char 0)" api_err = "Error in API call [401] - Authorization Required" parsed_err = parse_demisto_exception(DemistoException(api_err, json_err)) assert str(parsed_err) == str(DemistoException("Authorization error, check your credentials.")) def test_parse_demisto_exception_json_parse_error(self): from Infoblox import parse_demisto_exception json_err = "Expecting value: line 1 column 1 (char 0)" api_err = f"Failed to parse json object from response: {SSL_ERROR}" parsed_err = parse_demisto_exception(DemistoException(api_err, json_err)) assert str(parsed_err) == str(DemistoException("Cannot connect to Infoblox server, check your proxy and connection.")) def test_parse_demisto_exception_api_error(self): from Infoblox import parse_demisto_exception api_err = f"Error in API call [400] - Bad Request\n {json.dumps(API_ERROR_OBJ)}" parsed_err = parse_demisto_exception(DemistoException(api_err)) assert str(parsed_err) == str(DemistoException("Duplicate object 'test123.com' of type zone exists in the database.")) def test_transform_ext_attrs_2_attrs(self): """ Test transform_ext_attrs with 2 comma-separated values. Given: - An input. When: - The input has 2 comma-separated key-value pairs. Then: - A list of 2 dictionaries is returned with the key-value pairs as expected. """ input = "IB Discovery Owned=EMEA,Site=Tel-Aviv" actual = transform_ext_attrs(input) expected = [{"*IB Discovery Owned": "EMEA"}, {"*Site": "Tel-Aviv"}] assert actual == expected def test_transform_ext_attrs_2_attrs_whitespace(self): """ Test transform_ext_attrs with 2 comma-separated values and whitespace in the key/value. Given: - An input. When: - The input has 2 comma-separated key-value pairs. - The input includes spaces in the key - The input includes spaces in the value Then: - A list of 2 dictionaries is returned with the key-value pairs as expected. """ input = " IB Discovery Owned=EMEA,Site= Tel-Aviv" actual = transform_ext_attrs(input) expected = [{"*IB Discovery Owned": "EMEA"}, {"*Site": "Tel-Aviv"}] assert actual == expected def test_transform_ext_attrs_2_attrs_comma_attr_end(self): """ Test transform_ext_attrs with 2 comma-separated values and comma in the end of value. Given: - An input. When: - The input has 2 comma-separated key-value pairs. - The input includes a comma in the value. Then: - A list of 2 dictionaries is returned with the key-value pairs as expected. """ input = "IB Discovery Owned=EMEA,Site=Tel-Aviv, Yafo" with pytest.raises( DemistoException, match=f"Unable to parse provided ext_attrs='{input}'. Expected format is 'ExtKey1=ExtVal1,ExtKeyN=ExtValN'", ): transform_ext_attrs(input) def test_transform_ext_attrs_2_attrs_comma_attr_beginning(self): """ Test transform_ext_attrs with 2 comma-separated values and comma in the beginning of value. Given: - An input. When: - The input has 2 comma-separated key-value pairs. - The input includes a comma in the value. Then: - A list of 2 dictionaries is returned with the key-value pairs as expected. """ input = "IB Discovery Owned=EMEA,Site=,Tel-Aviv" with pytest.raises( DemistoException, match=f"Unable to parse provided ext_attrs='{input}'. Expected format is 'ExtKey1=ExtVal1,ExtKeyN=ExtValN'", ): transform_ext_attrs(input) def test_transform_ext_attrs_no_delimiter(self): """ Test transform_ext_attrs when the input has no delimiter (comma). Given: - An input. When: - The input has no delimiter. Then: - Return a list with 1 entry. """ input = "Site=Tel-Aviv" actual = transform_ext_attrs(input) expected = [{"*Site": "Tel-Aviv"}] assert actual == expected def test_transform_ext_attrs_no_delimiter_no_equal_sign(self): """ Test transform_ext_attrs when the input has no delimiter (comma) and no equal sign. Given: - An input. When: - The input has no delimiter and no equal sign. Then: - Return an empty list. """ input = "SiteTel-Aviv" actual = transform_ext_attrs(input) assert not actual def test_transform_ipv4_range(self): from_address = "192.168.1.0" to_address = "192.168.1.254" actual = transform_ipv4_range(from_address, to_address) expected = {"ip_address>": "192.168.1.0", "ip_address<": "192.168.1.254"} assert actual == expected assign_params(actual) def test_transform_network_info_context(self): """ Test the output of the `transform_network_info_context` helper command when provided with only extattr """ input = json.loads( ( Path(__file__).parent.resolve() / "test_data" / "TestNetworkInfoOperations" / "get_network_return_fields_extattrs.json" ).read_text() ).get("result") actual = transform_network_info_context(input) assert len(actual) == 1 assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual[0] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual[0] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual[0] assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual[0] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in actual[0] def test_transform_network_info_context_additional_fields(self): """ Test the output of the `transform_network_info_context` helper command when provided with additional fields specified """ input = json.loads( ( Path(__file__).parent.resolve() / "test_data" / "TestNetworkInfoOperations" / "get_networks_return_fields_options_extattrs.json" ).read_text() ).get("result") # noqa: E501 actual = transform_network_info_context(input) assert len(actual) == 2 assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual[1] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual[1] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual[1] assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual[1] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY in actual[1] def test_transform_host_records_context(self): """ Test the output of the `transform_network_info_context` helper command when provided with additional fields specified """ input = json.loads( (Path(__file__).parent.resolve() / "test_data" / "TestHostRecordsOperations" / "get_records.json").read_text() ).get("result") # noqa: E501 actual = transform_host_records_context(input) assert len(actual) == 4 assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual[0] assert INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY in actual[0] assert INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY in actual[0] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual[0] def test_get_extended_attributes_context_valid_extattrs(self): """Test get_extended_attributes_context with valid extattrs""" input = ( json.loads( ( Path(__file__).parent.resolve() / "test_data" / "TestHostRecordsOperations" / "get_record_extattrs.json" ).read_text() ) .get("result")[0] .get("extattrs") ) # noqa: E501 actual = get_extended_attributes_context(input) expected = {"IB Discovery Owned": "EMEA", "Site": "Tel-Aviv"} assert actual == expected def test_get_extended_attributes_context_invalid_extattrs(self): """Test get_extended_attributes_context with invalid extattrs""" input = {"invalid": "attribute"} actual = get_extended_attributes_context(input) expected = {"invalid": "N/A"} assert actual == expected def test_get_extended_attributes_context_empty_extattrs(self): """Test get_extended_attributes_context with invalid extattrs""" actual = get_extended_attributes_context({}) assert not actual class TestZonesOperations: def test_create_response_policy_zone_command(self, mocker, requests_mock): from Infoblox import create_response_policy_zone_command mocker.patch.object(demisto, "params", return_value={}) requests_mock.post(f"{BASE_URL}zone_rp{REQUEST_PARAM_ZONE}", json=POST_NEW_ZONE_RESPONSE) args = {"FQDN": "test.com", "rpz_policy": "GIVEN", "rpz_severity": "WARNING", "substitute_name": "", "rpz_type": ""} human_readable, context, raw_response = create_response_policy_zone_command(client, args) assert ( human_readable == "### Infoblox Integration - Response Policy Zone: test.com has been created\n" "|Disable|FQDN|Reference ID|Rpz Policy|Rpz Severity|Rpz Type|View|\n" "|---|---|---|---|---|---|---|\n" "| false | test.com | zone_rp/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS50ZXN0:test.com/default " "| GIVEN | WARNING | LOCAL | default |\n" ) assert context == { "Infoblox.ResponsePolicyZones(val.FQDN && val.FQDN === obj.FQDN)": { "ReferenceID": "zone_rp/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS50ZXN0:test.com/default", "Disable": False, "FQDN": "test.com", "RpzPolicy": "GIVEN", "RpzSeverity": "WARNING", "RpzType": "LOCAL", "View": "default", } } assert raw_response == { "result": { "_ref": "zone_rp/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS50ZXN0:test.com/default", "disable": False, "fqdn": "test.com", "rpz_policy": "GIVEN", "rpz_severity": "WARNING", "rpz_type": "LOCAL", "view": "default", } } class TestIPOperations: CONTEXT_PATH = f"{INTEGRATION_CONTEXT_NAME}.{INTEGRATION_IP_CONTEXT_NAME}" # noqa: E501 VALID_IP_ADDRESS = "192.168.1.1" VALID_NETMASK = "192.168.1.0/24" VALID_IPV6_ADDRESS = "2001:db8::1" BASE_URL = f"{client._base_url}ipv4address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1" # noqa: E501 BASE_URL_IPV6 = f"{client._base_url}ipv6address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1" # noqa: E501 def test_get_ip_command_too_many_arguments(self): """ Test the command argument input when too many arguments are specified. Given: - The execution of the `get_ip_command` When: - The `ip`, `network`, `from_ip` and `to_ip` arguments are all provided. Then: - Ensure that a validation error is raised. """ with pytest.raises(ValueError, match="Please specify only one of the `ip`, `network` or `from_ip`/`to_ip` arguments"): get_ip_command( client, { "ip": self.VALID_IP_ADDRESS, "network": self.VALID_NETMASK, "from_ip": self.VALID_IP_ADDRESS, "to_ip": self.VALID_IP_ADDRESS, }, ) def test_get_ip_command_no_valid_argument_specified(self): """ Test the command argument input when no valid argument is specified. Given: - The execution of the `get_ip_command` When: - The `ip` argument is not provided. - The `network` argument is not provided. - The `from_ip` argument is not provided. - The `to_ip` argument is not provided. Then: - Ensure that a validation error is raised. """ with pytest.raises(ValueError, match=("Please specify either the `ip`, `network` or `from_ip`/`to_ip` argument")): get_ip_command(client, {"status": IPv4AddressStatus.ACTIVE.value, "extended_attrs": "attr1=val1,attr2=val2"}) def test_get_ip_command_to_ip_defined_from_ip_not_defined(self): """ Test the command argument input when the `to_ip` argument is specified without the `from_ip` argument. Given: - The execution of the `get_ip_command` When: - The `ip` argument is not provided. - The `network` argument is not provided. - The `from_ip` argument is not provided. - The `to_ip` argument is provided. Then: - Ensure that a validation error is raised. """ with pytest.raises(ValueError, match=("Please specify either the `ip`, `network` or `from_ip`/`to_ip` argument")): get_ip_command(client, {"to_ip": self.VALID_IP_ADDRESS, "extended_attrs": "attr1=val1,attr2=val2"}) def test_get_ip_command_from_ip_not_to_ip(self): """ Test the command argument input when `from_ip` is specified but not `to_ip`. Given: - The execution of the `get_ip_command` When: - The `from_ip` argument is provided but `to_ip` is not. Then: - Ensure that a validation error is raised. """ with pytest.raises(ValueError, match="Please specify either the `ip`, `network` or `from_ip`/`to_ip` argument"): get_ip_command(client, {"from_ip": "1.1.1.1"}) def test_get_ip_command_from_ip_address_no_status_no_extattr(self, requests_mock): """ Test retrieval of an IP address in case it's provided. Given: - A mock response. When: - The IP address is provided. - No status is specified. - No extended attributes are specified. Then: - The human readable includes the input IP address. - The context includes the IP address object with the input IP address. """ ip = self.VALID_IP_ADDRESS mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_address_from_ip_address.json" ).read_text() requests_mock.get( f"{client._base_url}ipv4address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&ip_address={self.VALID_IP_ADDRESS}", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_ip_command(client, {"ip": ip}) actual_hr_lines = actual_hr.splitlines() assert "Infoblox Integration" in actual_hr_lines[0] assert self.VALID_IP_ADDRESS in actual_hr_lines[3] actual_output = cast(list, actual_context.get(self.CONTEXT_PATH)) assert len(actual_output) == 1 assert actual_output[0].get("IpAddress") == self.VALID_IP_ADDRESS assert actual_raw_response == json.loads(mock_response) def test_get_ip_command_from_ip_address_status_defined_no_extattr(self, requests_mock): """ Test retrieval of an IP address in case it's provided alongside the status. Given: - A mock response. When: - The IP address is provided. - The status is specified. - No extended attributes are specified. Then: - The human readable includes the input IP address. - The context includes the IP address object with the input IP address. """ ip = self.VALID_IP_ADDRESS mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_address_from_ip_address.json" ).read_text() requests_mock.get( f"{client._base_url}ipv4address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&ip_address={self.VALID_IP_ADDRESS}&status={IPv4AddressStatus.USED.value}", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_ip_command( client, {"ip": ip, "status": IPv4AddressStatus.USED.value} ) actual_hr_lines = actual_hr.splitlines() assert "Infoblox Integration" in actual_hr_lines[0] assert self.VALID_IP_ADDRESS in actual_hr_lines[3] assert self.CONTEXT_PATH in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_PATH)) assert len(actual_output) == 1 assert actual_output[0].get("Status") == IPv4AddressStatus.USED.value assert actual_raw_response == json.loads(mock_response) def test_get_ip_command_ip_range(self, requests_mock): """ Test the output of the `get_ip_command` when supplied a valid IP range. Given: - A mock response with a range of IPs. When: - The `from_ip` argument is set and valid. - The `to_ip` argument is set and valid. Then: - 10 IP addresses are returned. """ from_ip = self.VALID_IP_ADDRESS to_ip = self.VALID_IP_ADDRESS[:-1] + "9" mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_addresses_from_network.json" ).read_text() requests_mock.get( f"{self.BASE_URL}&ip_address>={from_ip}&ip_address<={to_ip}&_max_results={INTEGRATION_MAX_RESULTS_DEFAULT}", json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_ip_command(client, {"from_ip": from_ip, "to_ip": to_ip}) assert "Infoblox Integration" in actual_hr.splitlines()[0] assert self.CONTEXT_PATH in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_PATH)) assert len(actual_output) == 9 assert from_ip == actual_output[0].get("IpAddress") assert to_ip == actual_output[-1].get("IpAddress") assert INTEGRATION_COMMON_REFERENCE_ID_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_REFERENCE_ID_CONTEXT_KEY in actual_output[0] assert actual_raw_response == json.loads(mock_response) # TODO def test_get_ip_command_from_ip_status_defined_extattr_defined(self): """ Test retrieval of an IP address in case it's provided alongside the status and extended attributes. Given: - A mock response. When: - The IP address is provided. - The status is specified. - Extended attributes are specified. Then: - The human readable includes the input IP address. - The context includes the IP address object with the input IP address and specified status and extended attributes. """ # TODO def test_get_ip_command_from_ip_invalid_extattr(self, requests_mock): """ Test retrieval of an IP address with invalid extended attributes. Given: - A mock response. When: - The IP address is provided. - The status is not specified. - Invalid extended attributes are specified. Then: - Ensure a validation error is raised. """ def test_get_ip_command_from_netmask(self, requests_mock): """ Test retrieval of an IP address in case netmask is provided. Given: - A mock response. When: - The netmask is provided. - No status is specified. - No extended attributes are specified. Then: - The human readable includes the input netmask. - The context includes the IP address object with the input netmask. """ network = self.VALID_NETMASK mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_addresses_from_network.json" ).read_text() requests_mock.get( f"{client._base_url}ipv4address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&network={self.VALID_NETMASK}&status={IPv4AddressStatus.USED.value}", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_ip_command(client, {"network": network}) actual_hr_lines = actual_hr.splitlines() assert "Infoblox Integration" in actual_hr_lines[0] assert self.CONTEXT_PATH in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_PATH)) assert len(actual_output) == 9 def test_transform_ip_context_known_keys(self): """ Test for a scenario when the `infoblox-get-ip` command returns a expected/known response keys/. Given: - A mock response from InfoBlox API. When: - The response has all expected attributes. Then: - The transformation keys are as expected. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_address_from_ip_address.json" ).read_text() res: dict[str, list] = json.loads(mock_response) ip: list[dict[str, Any]] = res.get("result") actual_transformation = transform_ip_context(ip) assert len(actual_transformation) == 1 for k, v in IP_MAPPING.items(): assert actual_transformation[0].get(v) == ip[0].get(k) def test_transform_ip_context_unknown_key(self): """ Test for a scenario when the `infoblox-get-ip` command returns an unexpected/unknown key and the program doesn't raise an exception. See https://jira-dc.paloaltonetworks.com/browse/XSUP-35724. Given: - A mock response from InfoBlox API. When: - The response has an attribute, lease_state="active" that is unknown. Then: - The context output includes the transformed unknown key "LeaseState". """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv4_address_from_ip_address.json" ).read_text() unknown_key = "lease_state" unknown_key_value = "active" res: dict[str, list] = json.loads(mock_response) res["result"][0][unknown_key] = unknown_key_value ip = res.get("result") actual_transformation = transform_ip_context(ip) assert len(actual_transformation) == 1 for k, v in IP_MAPPING.items(): assert actual_transformation[0].get(v) == ip[0].get(k) assert actual_transformation[0]["LeaseState"] == unknown_key_value def test_get_ip_command_from_ipv6(self, requests_mock): """ Test retrieval of an IP address in case it's provided alongside the status and extended attributes. Given: - A mock response. When: - The IP address is provided. - The status is specified. - Extended attributes are specified. Then: - The human readable includes the input IP address. - The context includes the IP address object with the input IP address and specified status and extended attributes. """ ip = self.VALID_IPV6_ADDRESS mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_ipv6_address_from_ip_address.json" ).read_text() requests_mock.get( f"{client._base_url}ipv6address?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&ip_address={self.VALID_IPV6_ADDRESS}", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_ip_command(client, {"ip": ip}) actual_hr_lines = actual_hr.splitlines() assert "Infoblox Integration" in actual_hr_lines[0] assert self.VALID_IPV6_ADDRESS in actual_hr_lines[3] actual_output = cast(list, actual_context.get(self.CONTEXT_PATH)) assert len(actual_output) == 1 assert actual_output[0].get("IpAddress") == self.VALID_IPV6_ADDRESS assert actual_raw_response == json.loads(mock_response) class TestHostRecordsOperations: CONTEXT_KEY = f"{INTEGRATION_CONTEXT_NAME}.{INTEGRATION_HOST_RECORDS_CONTEXT_NAME}" GET_HOST_RECORDS_ENDPOINT = "record:host" def test_get_all_records(self, requests_mock): """ Test to get all records. Given: - Mock response for get all host records API call. When: - Get all host records API call is made. Then: - Ensure records are returned as expected. """ mock_response = (Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_records.json").read_text() requests_mock.get( f"{client._base_url}{self.GET_HOST_RECORDS_ENDPOINT}?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1", json=json.loads(mock_response), ) hr, records, _ = get_host_records_command(client, {}) assert len(cast(list, records.get(self.CONTEXT_KEY))) == 4 assert "Host records" in hr assert "extattrs" not in hr def test_get_records_from_hostname(self, requests_mock): """ Test to get host records by hostname. Given: - Mock response for get host records by hostname API call. When: - Get host records by hostname API call is made with hostname "host1". Then: - Ensure only matching record is returned. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_record_by_hostname.json" ).read_text() host_name = "ciac-3607.test" requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1&name={host_name}", json=json.loads(mock_response), ) hr, records, _ = get_host_records_command(client, {"host_name": host_name}) assert len(cast(list, records.get(self.CONTEXT_KEY))) == 1 assert "Host records" in hr assert "extattrs" not in hr def test_get_records_from_extattr(self, requests_mock): """ Test to get host records by extension attribute. Given: - Mock response for get host records by hostname API call. When: - An extension attribute is specified. Then: - Ensure only matching record is returned. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_record_extattrs.json" ).read_text() input = "Site=Tel-Aviv" requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?_return_fields%2B=extattrs&*{input}", json=json.loads(mock_response), ) hr, records, _ = get_host_records_command(client, {"extattrs": input}) assert len(cast(list, records.get(self.CONTEXT_KEY))) == 1 assert "Host records" in hr assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in hr def test_get_rerords_with_extattrs_aliases(self, requests_mock): """ Test to get host records with extended attributes and additional fields. Given: - Mock response for get host records by. When: - We specify extattrs and aliases as additional fields. Then: - 4 records are returned. - The 3 records don't include an AdditionalFields key. - The last record includes an AdditionalFields key. """ input = "extattrs,aliases" mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_records_extattrs_aliases.json" ).read_text() requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?_return_fields%2B={input}", json=json.loads(mock_response) ) actual_hr, actual_records, actual_raw_response = get_host_records_command(client, {"additional_return_fields": input}) assert "Host records" in actual_hr assert self.CONTEXT_KEY in actual_records actual_output = cast(list, actual_records.get(self.CONTEXT_KEY)) assert len(actual_output) == 4 first_record = actual_output[0] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in first_record assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in first_record assert INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY in first_record assert first_record[INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY] == "192.168.10.10" assert INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY in first_record assert not first_record[INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in first_record assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in first_record second_record = actual_output[1] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in second_record assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in second_record assert INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY in second_record assert second_record[INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY] == "192.168.100.100" assert INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY in second_record assert second_record[INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in second_record assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in second_record third_record = actual_output[2] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in third_record assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in third_record assert INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY in third_record assert INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY in third_record assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in third_record assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in third_record fourth_record = actual_output[3] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in fourth_record assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in fourth_record assert INTEGRATION_HOST_RECORDS_IPV4ADDRESS_CONTEXT_KEY in fourth_record assert INTEGRATION_HOST_RECORDS_CONFIGURE_FOR_DHCP_KEY_CONTEXT_KEY in fourth_record assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in fourth_record assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY in fourth_record assert actual_raw_response == json.loads(mock_response) def test_get_host_records_command_non_existing_additional_return_fields(self, requests_mock): """ Test running `get_host_records_command` with a non-existing return field. Given: - An `additional_return_fields` is specified. When: - The `additional_return_fields` doesn't exist. Then: - A `DemistoException` is raised. """ additional_return_fields = "none" mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "unknown_argument.json" ).read_text() requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?_return_fields%2B={additional_return_fields}", json=json.loads(mock_response), ) with pytest.raises(DemistoException, match="Unknown argument/field: 'none'"): get_host_records_command(client, {"additional_return_fields": additional_return_fields}) def test_get_host_records_command_no_additional_fields(self, requests_mock): """ Given: - Running `get_host_records_command`. When: - No `additional_fields` are provided. Then: - The `extattrs` should appended and returned. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_records_extattrs.json" ).read_text() requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?_return_fields%2B={INTEGRATION_COMMON_RAW_RESULT_EXTENSION_ATTRIBUTES_KEY}", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_host_records_command(client, {}) assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_hr.splitlines()[1] assert self.CONTEXT_KEY in actual_context actual_output = actual_context.get(self.CONTEXT_KEY) for o in actual_output: assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in o def test_get_host_records_command_extattrs_set_additional_fields(self, requests_mock): """ Given: - Running `get_host_records_command`. When: - `additional_fields` is set to `extattrs`. Then: - The `extattrs` should appended and returned. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_records_extattrs.json" ).read_text() requests_mock.get( client._base_url + self.GET_HOST_RECORDS_ENDPOINT + f"?_return_fields%2B={INTEGRATION_COMMON_RAW_RESULT_EXTENSION_ATTRIBUTES_KEY}&*Site=ciac-5843", # noqa: E501 json=json.loads(mock_response), ) actual_hr, actual_context, actual_raw_response = get_host_records_command( client, { "additional_return_fields": INTEGRATION_COMMON_RAW_RESULT_EXTENSION_ATTRIBUTES_KEY, INTEGRATION_COMMON_RAW_RESULT_EXTENSION_ATTRIBUTES_KEY: "Site=ciac-5843", }, ) assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_hr.splitlines()[1] assert self.CONTEXT_KEY in actual_context actual_output = actual_context.get(self.CONTEXT_KEY) for o in actual_output: assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in o class TestNetworkInfoOperations: CONTEXT_KEY = f"{INTEGRATION_CONTEXT_NAME}.{INTEGRATION_NETWORK_INFO_CONTEXT_KEY}" BASE_URL = f"{client._base_url}network?{InfoBloxNIOSClient.REQUEST_PARAMS_RETURN_AS_OBJECT_KEY}=1" # noqa: E501 def test_get_network_info_command(self, requests_mock): """ Test when no arguments are supplied to the `get_network_info_command` method. Given: - A mock response of 2 networks. When: - No arguments are provided. Then: - 50 limit is applied. - output has 2 networks. - raw response is equal to the mock response. - No additional fields context key is set. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_networks.json" ).read_text() requests_mock.get(self.BASE_URL, json=json.loads(mock_response)) actual_hr, actual_context, actual_raw_response = get_network_info_command(client, {}) assert "Network information" in actual_hr assert self.CONTEXT_KEY in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_KEY)) assert len(actual_output) == 2 assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in actual_output[0] assert actual_raw_response == json.loads(mock_response) def test_get_network_pattern_specified_return_fields_extattrs(self, requests_mock): """ Test when an argument is supplied to the `get_network_info_command` method. Given: - A mock response of 1 network. When: - The `pattern` argument is supplied. Then: - 50 limit is applied. - output has 1 networks. - raw response is equal to the mock response. - No additional fields context key is set. """ mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_network_return_fields_extattrs.json" ).read_text() pattern = "192.168." requests_mock.get( f"{self.BASE_URL}&_max_results=50&_return_fields%2B=extattrs&network~={pattern}", json=json.loads(mock_response) ) actual_hr, actual_context, actual_raw_response = get_network_info_command(client, {"pattern": pattern}) assert "Network information" in actual_hr assert self.CONTEXT_KEY in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_KEY)) assert len(actual_output) == 1 assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY not in actual_output[0] assert actual_raw_response == json.loads(mock_response) def test_get_networks_return_fields_options_extattrs(self, requests_mock): """ Test when an arguments is supplied to the `get_network_info_command` method. Given: - A mock response of 2 networks. When: - `additional_return_fields` argument is set. - `max_results` argument is set. Then: - 10 limit is applied. - output has 1 networks. - raw response is equal to the mock response. - The additional fields context key is set. """ limit = 10 mock_response = ( Path(__file__).parent.resolve() / "test_data" / self.__class__.__name__ / "get_networks_return_fields_options_extattrs.json" ).read_text() requests_mock.get(self.BASE_URL, json=json.loads(mock_response)) actual_hr, actual_context, actual_raw_response = get_network_info_command( client, {"additional_return_fields": "extattrs,options", "max_results": limit} ) assert "Network information" in actual_hr assert self.CONTEXT_KEY in actual_context actual_output = cast(list, actual_context.get(self.CONTEXT_KEY)) assert len(actual_output) == 2 assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_EXTENSION_ATTRIBUTES_CONTEXT_KEY in actual_output[1] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NAME_CONTEXT_KEY in actual_output[1] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_REFERENCE_CONTEXT_KEY in actual_output[1] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_NETWORKVIEW_CONTEXT_KEY in actual_output[1] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY in actual_output[0] assert INTEGRATION_COMMON_ADDITIONAL_FIELDS_CONTEXT_KEY in actual_output[1] assert actual_raw_response == json.loads(mock_response) class TestUpdateRpzRuleCommand: """Test class for update_rpz_rule_command function.""" def test_update_rpz_rule_passthru_success(self, requests_mock): """ Test successful update of a Passthru RPZ rule. Given: - Valid arguments for updating a Passthru rule - Mock response from the API When: - update_rpz_rule_command is called Then: - Rule is updated successfully - Correct context and human readable output are returned """ from Infoblox import update_rpz_rule_command reference_id = "record:rpz:cname/1234:test.test.com/default" mock_response = util_load_json(f"{self.__class__.__name__}/update_rpz_rule_passthru_success.json") requests_mock.put(f"{BASE_URL}{reference_id}", json=mock_response) args = { "reference_id": reference_id, "rule_type": "Passthru", "name": "test", "rp_zone": "test.com", "comment": "Updated passthru rule", "view": "default", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/update_rpz_rule_passthru_success_hr.md") hr, context, raw_response = update_rpz_rule_command(client, args) # Verify human readable output assert hr == expected_hr # Verify context expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ModifiedResponsePolicyZoneRules(val.Name && val.Name === obj.Name)" assert expected_context_key in context rule_context = context[expected_context_key] assert rule_context["Name"] == "test.test.com" assert rule_context["Zone"] == "test.com" assert rule_context["Comment"] == "Updated passthru rule" assert rule_context["ReferenceID"] == reference_id # Verify raw response assert raw_response == mock_response def test_update_rpz_rule_block_no_data_success(self, requests_mock): """ Test successful update of a Block (No data) RPZ rule. Given: - Valid arguments for updating a Block (No data) rule - Mock response from the API When: - update_rpz_rule_command is called Then: - Rule is updated successfully with canonical set to "*" """ from Infoblox import update_rpz_rule_command reference_id = "record:rpz:cname/1234:malicious.test.com/default" mock_response = util_load_json(f"{self.__class__.__name__}/update_rpz_rule_block_no_data_success.json") requests_mock.put(f"{BASE_URL}{reference_id}", json=mock_response) args = { "reference_id": reference_id, "rule_type": "Block (No data)", "name": "malicious.test.com", "rp_zone": "test.com", "comment": "Block malicious domain", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/update_rpz_rule_block_no_data_success_hr.md") hr, context, raw_response = update_rpz_rule_command(client, args) # Verify the rule was updated correctly assert hr == expected_hr expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ModifiedResponsePolicyZoneRules(val.Name && val.Name === obj.Name)" rule_context = context[expected_context_key] assert rule_context["Name"] == "malicious.test.com" assert rule_context["Comment"] == "Block malicious domain" assert mock_response == raw_response def test_update_rpz_rule_block_no_domain_success(self, requests_mock): """ Test successful update of a Block (No such domain) RPZ rule. Given: - Valid arguments for updating a Block (No such domain) rule - Mock response from the API When: - update_rpz_rule_command is called Then: - Rule is updated successfully with canonical set to empty string """ from Infoblox import update_rpz_rule_command reference_id = "record:rpz:cname/1234:blocked.test.com/default" mock_response = util_load_json(f"{self.__class__.__name__}/update_rpz_rule_block_no_domain_success.json") requests_mock.put(f"{BASE_URL}{reference_id}", json=mock_response) args = { "reference_id": reference_id, "rule_type": "Block (No such domain)", "name": "blocked", "rp_zone": "test.com", "comment": "Block domain completely", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/update_rpz_rule_block_no_domain_success_hr.md") hr, context, raw_response = update_rpz_rule_command(client, args) # Verify the rule was updated correctly assert hr == expected_hr expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ModifiedResponsePolicyZoneRules(val.Name && val.Name === obj.Name)" rule_context = context[expected_context_key] assert rule_context["Name"] == "blocked.test.com" def test_update_rpz_rule_substitute_success(self, requests_mock): """ Test successful update of a Substitute (domain name) RPZ rule. Given: - Valid arguments for updating a Substitute rule with substitute_name - Mock response from the API When: - update_rpz_rule_command is called Then: - Rule is updated successfully with canonical set to substitute_name """ from Infoblox import update_rpz_rule_command reference_id = "record:rpz:cname/1234:redirect.test.com/default" mock_response = util_load_json(f"{self.__class__.__name__}/update_rpz_rule_substitute_success.json") requests_mock.put(f"{BASE_URL}{reference_id}", json=mock_response) args = { "reference_id": reference_id, "rule_type": "Substitute (domain name)", "name": "redirect.test.com", "rp_zone": "test.com", "comment": "Redirect to safe domain", "substitute_name": "safe.example.com", } hr, context, raw_response = update_rpz_rule_command(client, args) # Verify the rule was updated correctly assert "redirect.test.com has been updated" in hr expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ModifiedResponsePolicyZoneRules(val.Name && val.Name === obj.Name)" rule_context = context[expected_context_key] assert rule_context["Name"] == "redirect.test.com" assert rule_context["Comment"] == "Redirect to safe domain" def test_update_rpz_rule_substitute_missing_substitute_name_error(self): """ Test error when updating Substitute rule without substitute_name. Given: - Arguments for Substitute rule without substitute_name When: - update_rpz_rule_command is called Then: - DemistoException is raised with appropriate error message """ from Infoblox import update_rpz_rule_command args = { "reference_id": "record:rpz:cname/test", "rule_type": "Substitute (domain name)", "name": "test.test.com", "rp_zone": "test.com", "comment": "Test substitute rule", } with pytest.raises(DemistoException) as exc_info: update_rpz_rule_command(client, args) assert "Substitute (domain name) rules requires a substitute name argument" in str(exc_info.value) class TestListResponsePolicyZonesCommand: """Test class for list_response_policy_zones_command function.""" def test_list_zones_with_fqdn_filter(self, requests_mock): """ Test listing response policy zones with FQDN filter. Given: - FQDN filter argument provided - Mock response with single matching zone When: - list_response_policy_zones_command is called Then: - Only matching zone is returned """ from Infoblox import list_response_policy_zones_command mock_response = util_load_json(f"{self.__class__.__name__}/list_zones_with_fqdn_filter.json") requests_mock.get( f"{BASE_URL}zone_rp?_return_as_object=1&_return_fields%2B=fqdn%2Crpz_policy%2Crpz_severity%2Crpz_type%2Csubstitute_name%2Ccomment%2Cdisable&_max_results=50&fqdn=test.com", json=mock_response, ) args = {"fqdn": "test.com"} expected_hr = util_load_text_data(f"{self.__class__.__name__}/list_zones_with_fqdn_filter_hr.md") hr, context, raw_response = list_response_policy_zones_command(client, args) # Verify only one zone is returned expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ResponsePolicyZones(val.FQDN && val.FQDN === obj.FQDN)" zones_context = context[expected_context_key] assert len(zones_context) == 1 assert zones_context[0]["FQDN"] == "test.com" assert hr == expected_hr def test_list_zones_with_view_filter(self, requests_mock): """ Test listing response policy zones with view filter. Given: - View filter argument provided - Mock response with zone in specified view When: - list_response_policy_zones_command is called Then: - Only zones in specified view are returned """ from Infoblox import list_response_policy_zones_command mock_response = util_load_json(f"{self.__class__.__name__}/list_zones_with_view_filter.json") requests_mock.get( f"{BASE_URL}zone_rp?_return_as_object=1&_return_fields%2B=fqdn%2Crpz_policy%2Crpz_severity%2Crpz_type%2Csubstitute_name%2Ccomment%2Cdisable&_max_results=50&view=external", json=mock_response, ) args = {"view": "external"} expected_hr = util_load_text_data(f"{self.__class__.__name__}/list_zones_with_view_filter_hr.md") hr, context, raw_response = list_response_policy_zones_command(client, args) # Verify zone with external view is returned expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ResponsePolicyZones(val.FQDN && val.FQDN === obj.FQDN)" zones_context = context[expected_context_key] assert len(zones_context) == 1 assert zones_context[0]["View"] == "external" assert zones_context[0]["RpzPolicy"] == "SUBSTITUTE" assert hr == expected_hr def test_list_zones_multiple_filters(self, requests_mock): """ Test listing response policy zones with multiple filter arguments. Given: - Multiple filter arguments (fqdn, view, comment) - Mock response with matching zone When: - list_response_policy_zones_command is called Then: - Filters are properly applied and matching zone is returned """ from Infoblox import list_response_policy_zones_command mock_response = util_load_json(f"{self.__class__.__name__}/list_zones_multiple_filters.json") requests_mock.get( f"{BASE_URL}zone_rp?_return_as_object=1&_return_fields%2B=fqdn%2Crpz_policy%2Crpz_severity%2Crpz_type%2Csubstitute_name%2Ccomment%2Cdisable&_max_results=25&view=default", json=mock_response, ) args = { "max_results": "25", "view": "default", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/list_zones_multiple_filters_hr.md") hr, context, raw_response = list_response_policy_zones_command(client, args) # Verify zone matching all filters is returned expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.ResponsePolicyZones(val.FQDN && val.FQDN === obj.FQDN)" zones_context = context[expected_context_key] assert len(zones_context) == 3 assert zones_context[0]["View"] == "default" assert zones_context[0]["Comment"] == "Test response policy zone" assert hr == expected_hr class TestCreateHostRecordCommand: """Test class for create_host_record_command function.""" def test_create_host_record_success(self, requests_mock): """ Test successful creation of a host record with both IPv4 and IPv6 addresses. Given: - Valid arguments for creating a host record - Mock response from the API When: - create_host_record_command is called Then: - Host record is created successfully - Correct context and human readable output are returned """ from Infoblox import create_host_record_command mock_response = util_load_json(f"{self.__class__.__name__}/create_host_record_success.json") # Construct URL with query parameters using quote base_endpoint = "record:host" return_fields = ( "aliases,allow_telnet,cli_credentials,cloud_info,comment,configure_for_dns,ddns_protected," "device_description,device_location,device_type,device_vendor,disable,disable_discovery," "dns_aliases,dns_name,extattrs,ipv4addrs,ipv6addrs,ms_ad_user_data,name,network_view," "rrset_order,snmp3_credential,snmp_credential,ttl,use_cli_credentials,use_snmp3_credential," "use_snmp_credential,use_ttl,view,zone" ) url = f"{BASE_URL}{base_endpoint}?_return_as_object=1&_return_fields%2B={quote(return_fields)}" requests_mock.post( url, json=mock_response, ) args = { "name": "example", "ipv4_address": '[{"ipv4addr": "0.0.0.1"}]', "ipv6_address": '[{"ipv6addr": "0000:000:0000::0000:000:001", "configure_for_dhcp": false}]', "configure_for_dns": "false", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/create_host_record_success_hr.md") hr, context, raw_response = create_host_record_command(client, args) # Verify human readable output assert hr == expected_hr # Verify context expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.Host(val.Name && val.Name === obj.Name)" assert expected_context_key in context host_context = context[expected_context_key] assert host_context["Name"] == "example" assert host_context["ReferenceID"] == "record:host/45678:example/ " assert host_context["Type"] == "record:host" assert not host_context["ConfigureForDNS"] assert host_context["IPV4Addresses"][0]["IPV4Address"] == "0.0.0.1" assert host_context["IPV6Addresses"][0]["IPV6Address"] == "0000:000:0000::0000:000:001" # Verify raw response assert raw_response == mock_response def test_create_host_record_ipv4_only_success(self, requests_mock): """ Test successful creation of a host record with IPv4 address only. Given: - Valid arguments for creating a host record with IPv4 only - Mock response from the API When: - create_host_record_command is called Then: - Host record is created successfully with IPv4 address """ from Infoblox import create_host_record_command mock_response = util_load_json(f"{self.__class__.__name__}/create_host_record_ipv4_only_success.json") # Construct URL with query parameters using quote base_endpoint = "record:host" return_fields = ( "aliases,allow_telnet,cli_credentials,cloud_info,comment,configure_for_dns,ddns_protected," "device_description,device_location,device_type,device_vendor,disable,disable_discovery," "dns_aliases,dns_name,extattrs,ipv4addrs,ipv6addrs,ms_ad_user_data,name,network_view," "rrset_order,snmp3_credential,snmp_credential,ttl,use_cli_credentials,use_snmp3_credential," "use_snmp_credential,use_ttl,view,zone" ) url = f"{BASE_URL}{base_endpoint}?_return_as_object=1&_return_fields%2B={quote(return_fields)}" requests_mock.post( url, json=mock_response, ) args = { "name": "testhost", "ipv4_address": '[{"ipv4addr": "192.168.1.2", "configure_for_dhcp": true}]', "comment": "Test host record", "configure_for_dns": "true", } expected_hr = util_load_text_data(f"{self.__class__.__name__}/create_host_record_ipv4_only_success_hr.md") hr, context, raw_response = create_host_record_command(client, args) # Verify the host was created correctly assert hr == expected_hr expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.Host(val.Name && val.Name === obj.Name)" host_context = context[expected_context_key] assert host_context["Name"] == "testhost" assert host_context["Comment"] == "Test host record" assert host_context["ConfigureForDNS"] assert host_context["IPV4Addresses"][0]["IPV4Address"] == "192.168.1.2" assert raw_response == mock_response class TestDhcpLeaseLookupCommand: """Test class for dhcp_lease_lookup_command function.""" def test_dhcp_lease_lookup_success(self, requests_mock): """ Test successful DHCP lease lookup with limit parameter. Given: - Valid arguments for DHCP lease lookup - Mock response from the API with multiple leases When: - dhcp_lease_lookup_command is called Then: - Multiple DHCP leases are returned successfully - Correct context and human readable output are returned """ from Infoblox import dhcp_lease_lookup_command mock_response = util_load_json(f"{self.__class__.__name__}/dhcp_lease_lookup_success.json") # Construct URL with query parameters using quote base_endpoint = "lease" return_fields = "address,billing_class,binding_state,client_hostname,cltt,discovered_data,ends,hardware,ipv6_duid,ipv6_iaid,ipv6_preferred_lifetime,ipv6_prefix_bits,is_invalid_mac,ms_ad_user_data,network,network_view,never_ends,never_starts,next_binding_state,on_commit,on_expiry,on_release,option,protocol,remote_id,served_by,server_host_name,starts,tsfp,tstp,uid,username,variable,fingerprint" # noqa: E501 url = f"{BASE_URL}{base_endpoint}?_return_as_object=1&_return_fields%2B={quote(return_fields)}&_max_results=3" requests_mock.get( url, json=mock_response, ) args = {"limit": "3"} expected_hr = util_load_text_data(f"{self.__class__.__name__}/dhcp_lease_lookup_success_hr.md") hr, context, raw_response = dhcp_lease_lookup_command(client, args) # Verify human readable output assert hr == expected_hr # Verify context expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.DHCPLease(val.Address && val.Address === obj.Address)" assert expected_context_key in context leases_context = context[expected_context_key] assert len(leases_context) == 3 assert leases_context[0]["Address"] == "0.0.0.5" assert leases_context[0]["BindingState"] == "FREE" assert leases_context[1]["Address"] == "0.0.0.3" assert leases_context[1]["BindingState"] == "ACTIVE" # Verify raw response assert raw_response == mock_response def test_dhcp_lease_lookup_by_ip_success(self, requests_mock): """ Test successful DHCP lease lookup by specific IP address. Given: - Valid IP address argument for DHCP lease lookup - Mock response from the API with single lease When: - dhcp_lease_lookup_command is called Then: - Single DHCP lease is returned successfully """ from Infoblox import dhcp_lease_lookup_command mock_response = util_load_json(f"{self.__class__.__name__}/dhcp_lease_lookup_by_ip_success.json") # Construct URL with query parameters using quote base_endpoint = "lease" return_fields = "address,billing_class,binding_state,client_hostname,cltt,discovered_data,ends,hardware,ipv6_duid,ipv6_iaid,ipv6_preferred_lifetime,ipv6_prefix_bits,is_invalid_mac,ms_ad_user_data,network,network_view,never_ends,never_starts,next_binding_state,on_commit,on_expiry,on_release,option,protocol,remote_id,served_by,server_host_name,starts,tsfp,tstp,uid,username,variable,fingerprint" # noqa: E501 url = f"{BASE_URL}{base_endpoint}?_return_as_object=1&_return_fields%2B={quote(return_fields)}&address=0.0.0.3" requests_mock.get( url, json=mock_response, ) args = {"ip_address": "0.0.0.3"} expected_hr = util_load_text_data(f"{self.__class__.__name__}/dhcp_lease_lookup_by_ip_success_hr.md") hr, context, raw_response = dhcp_lease_lookup_command(client, args) # Verify the lease was found correctly assert hr == expected_hr expected_context_key = f"{INTEGRATION_CONTEXT_NAME}.DHCPLease(val.Address && val.Address === obj.Address)" leases_context = context[expected_context_key] assert len(leases_context) == 1 assert leases_context[0]["Address"] == "0.0.0.3" assert leases_context[0]["ClientHostname"] == "CE" assert leases_context[0]["Hardware"] == "00:00:00:00:00:03" assert raw_response == mock_response