InfobloxThreatIntelligenceFeed

The Infoblox Threat Intelligence Feed retrieves the discovered indicators from the Infoblox platform based on user-specified filters.

Data Enrichment & Threat Intelligence · Infoblox Threat Intelligence Feed · Feed

Details

IDInfobloxThreatIntelligenceFeed
ProviderInfoblox
CategoryData Enrichment & Threat Intelligence
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658

README

The Infoblox Threat Intelligence Feed retrieves the discovered indicators from the Infoblox platform based on user-specified filters.
This integration was integrated and tested with version 1.0.0 of InfobloxThreatIntelligenceFeed.

Configure Infoblox Threat Intelligence Feed in Cortex

Parameter Description Required
Service API Key Service API key for Infoblox TIDE API authentication True
Fetch indicators   False
Indicator Types The type of indicators to be retrieved. False
First Fetch Time The date or relative timestamp from where to start fetching indicators.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2025, 01 Mar 2025 04:45:33, 2025-05-17T04:45:33Z

Note: The maximum allowed relative time is 4 hours or 240 minutes.
False
Max Indicators Per Fetch The maximum number of indicators to fetch in each run.

Note: The maximum allowed value is 50000.
False
DGA Threat Filter the indicators having threats originated from dynamically generated algorithms. False
Threat Classes Filters the indicators according to the selected threat classes. False
Data Providers Filter indicators by data provider profiles. False
Indicator Reputation Indicators from this integration instance will be marked with this reputation. False
Source Reliability Reliability of the source providing the intelligence data. True
Traffic Light Protocol Color The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. False
Tags Supports CSV values. False
Bypass exclusion list When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. False
Indicator Expiration Method   False
Feed Fetch Interval Time interval for fetching indicators.

Note: The maximum allowed interval is 4 hours or 240 minutes.
False
Trust any certificate (not secure)   False
Use system proxy settings   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.

infoblox-cloud-get-indicators


Fetches a given limit of indicators from the Infoblox platform and displays them in human-readable format in the war room.

Base Command

infoblox-cloud-get-indicators

Input

Argument Name Description Required
limit The maximum number of indicators to retrieve.

Note: The maximum allowed value is 50000. Default is 50.
Optional
indicator_types The type of indicators to be retrieved. Supports comma-separated values. Possible values are: IP, HOST, URL, EMAIL, HASH. Optional
from_date The date or relative timestamp from which indicator retrieval begins.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2025, 01 Mar 2025 04:45:33, 2025-05-17T04:45:33Z.
Optional
to_date The date or relative timestamp up to which indicator retrieval ends.

Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ

For example: 01 Mar 2025, 01 Mar 2025 04:45:33, 2025-05-17T04:45:33Z.
Optional
dga_threat Filter the indicators having threats originated from dynamically generated algorithms. Possible values are: Yes, No. Optional
threat_classes Filters the indicators according to the provided threat classes. Supports comma-separated values. Possible values are: APT, Bot, CompromisedDomain, CompromisedHost, Cryptocurrency, DDoS, DNSTunnel, ExploitKit, ICS, IllegalContent, InternetInfrastructure, IntrusionAttempt, LimitedDistro, Malicious, MaliciousNameserver, MalwareC2, MalwareC2DGA, MalwareDownload, Parked, Phishing, Policy, PolicyViolation, Proxy, Scam, Sinkhole, Spambot, Suspicious, UncategorizedThreat, Undefined, UnwantedContent, WebAppAttack, Whitelist. Optional
data_provider_profiles Filters the indicators according to the given data providers. Supports comma-separated values. Possible values are: IID, AISCOMM. Optional

Context Output

Path Type Description
Infoblox.FeedIndicator.id String Unique identifier of the indicator.
Infoblox.FeedIndicator.type String Type of the indicator (HOST, IP, URL, EMAIL, HASH).
Infoblox.FeedIndicator.host String Hostname of the indicator.
Infoblox.FeedIndicator.domain String Domain of the indicator.
Infoblox.FeedIndicator.ip String IP address of the indicator.
Infoblox.FeedIndicator.email String Email address of the indicator.
Infoblox.FeedIndicator.hash String Hash of the indicator.
Infoblox.FeedIndicator.hash_type String Hash type of the indicator.
Infoblox.FeedIndicator.url String URL of the indicator.
Infoblox.FeedIndicator.value String The indicator value.
Infoblox.FeedIndicator.tld String Top-level domain of the indicator.
Infoblox.FeedIndicator.threat_level Number Threat level of the indicator (0-100).
Infoblox.FeedIndicator.threat_label String Threat label of the indicator.
Infoblox.FeedIndicator.confidence Number Confidence level of the indicator (0-100).
Infoblox.FeedIndicator.threat_class String Threat class of the indicator.
Infoblox.FeedIndicator.property String Property of the indicator.
Infoblox.FeedIndicator.profile String Profile of the indicator.
Infoblox.FeedIndicator.detected Date Detection timestamp.
Infoblox.FeedIndicator.received Date Reception timestamp.
Infoblox.FeedIndicator.expiration Date Expiration timestamp.
Infoblox.FeedIndicator.up String Status of the indicator.
Infoblox.FeedIndicator.dga String Domain generation algorithm.
Infoblox.FeedIndicator.batch_id String Batch ID of the indicator.
Infoblox.FeedIndicator.threat_score Number Threat score of the indicator.
Infoblox.FeedIndicator.threat_score_rating String Threat score rating of the indicator.
Infoblox.FeedIndicator.threat_score_vector String Threat score vector of the indicator.
Infoblox.FeedIndicator.confidence_score Number Confidence score of the indicator.
Infoblox.FeedIndicator.confidence_score_rating String Confidence score rating of the indicator.
Infoblox.FeedIndicator.confidence_score_vector String Confidence score vector of the indicator.
Infoblox.FeedIndicator.extended.notes String Notes for the indicator.
Infoblox.FeedIndicator.extended.comments String Comments for the indicator.
Infoblox.FeedIndicator.extended.cyberint_guid String GUID of the indicator.
Infoblox.FeedIndicator.extended.protocol String Protocol of the indicator.
Infoblox.FeedIndicator.extended.references String References of the indicator.
Infoblox.FeedIndicator.extended.original_profile String Original profile of the indicator.
Infoblox.FeedIndicator.extended.attack_chain String Attack chain of the indicator.
Infoblox.FeedIndicator.extended.sample_sha256 String SHA256 of the sample.

Command example

!infoblox-cloud-get-indicators limit="5" indicator_types="EMAIL,IP,HOST,HASH,URL" from_date="2023-01-01T00:00:00.000Z" to_date="2024-12-31T23:59:59.999Z"

Context Example

{
    "Infoblox": {
        "FeedIndicator": [
            {
                "id": "00000000-0000-0000-0000-000000000001",
                "type": "EMAIL",
                "host": "example.com",
                "email": "test@example.com",
                "domain": "example.com",
                "tld": "com",
                "profile": "IID",
                "property": "APT_testC2",
                "threat_class": "APT",
                "threat_level": 80,
                "expiration": "2043-01-06T00:41:57.421Z",
                "detected": "2023-01-11T00:41:57.421Z",
                "received": "2023-01-11T00:46:38.969Z",
                "imported": "2023-01-11T00:46:38.969Z",
                "up": "true",
                "confidence": 100,
                "batch_id": "00000000-0000-0000-0000-000000000001",
                "threat_score": 9.1,
                "threat_score_rating": "Critical",
                "threat_score_vector": "TSIS:1.0/AV:N/AC:L/PR:L/UI:N/EX:H/MOD:L/AVL:L/CI:N/ASN:N/TLD:N/DOP:N/P:T",
                "risk_score": 9.9,
                "risk_score_rating": "Critical",
                "risk_score_vector": "RSIS:1.0/TSS:C/TLD:N/CVSS:C/EX:H/MOD:L/AVL:L/T:H/DT:H",
                "confidence_score": 8.1,
                "confidence_score_rating": "High",
                "confidence_score_vector": "COSIS:1.0/SR:H/POP:N/TLD:N/CP:T",
                "extended": {
                    "cyberint_guid": "00000000000000000000000000000001",
                    "notes": "The email address is part of a cluster of test domains."
                }
            },
            {
                "id": "00000000-0000-0000-0000-000000000002",
                "type": "IP",
                "ip": "0.0.0.0",
                "profile": "IID",
                "property": "APT_testC2",
                "threat_class": "APT",
                "threat_level": 100,
                "expiration": "2042-11-01T09:29:18.721Z",
                "detected": "2022-11-01T09:29:18.721Z",
                "received": "2022-11-01T09:31:39.329Z",
                "imported": "2022-11-01T09:31:39.329Z",
                "up": "true",
                "confidence": 100,
                "batch_id": "00000000-0000-0000-0000-000000000002",
                "threat_score": 10,
                "threat_score_rating": "Critical",
                "threat_score_vector": "TSIS:1.0/AV:N/AC:L/PR:L/UI:N/EX:H/MOD:H/AVL:L/CI:N/ASN:N/TLD:N/DOP:N/P:T",
                "risk_score": 9.9,
                "risk_score_rating": "Critical",
                "risk_score_vector": "RSIS:1.0/TSS:C/TLD:N/CVSS:C/EX:H/MOD:H/AVL:L/T:H/DT:H",
                "confidence_score": 0.1,
                "confidence_score_rating": "Unconfirmed",
                "confidence_score_vector": "COSIS:1.0/SR:N/POP:N/TLD:N/CP:T",
                "extended": {
                    "cyberint_guid": "00000000000000000000000000000002",
                    "notes": "test notes for APT testC2."
                }
            },
            {
                "id": "00000000-0000-0000-0000-000000000003",
                "type": "HOST",
                "host": "test.net",
                "domain": "test.net",
                "tld": "net",
                "profile": "IID",
                "property": "MalwareC2_testRAT",
                "threat_class": "MalwareC2",
                "threat_level": 100,
                "threat_label": "LowProfileC2Beacon",
                "expiration": "2026-04-15T23:54:58.665Z",
                "detected": "2024-04-15T23:54:58.665Z",
                "received": "2024-04-17T16:14:57.694Z",
                "imported": "2024-04-17T16:14:57.694Z",
                "dga": "false",
                "up": "true",
                "confidence": 100,
                "batch_id": "00000000-0000-0000-0000-000000000003",
                "threat_score": 10,
                "threat_score_rating": "Critical",
                "threat_score_vector": "TSIS:1.0/AV:N/AC:L/PR:L/UI:N/EX:H/MOD:H/AVL:L/CI:N/ASN:N/TLD:N/DOP:N/P:T",
                "risk_score": 9.7,
                "risk_score_rating": "Critical",
                "risk_score_vector": "RSIS:1.0/TSS:C/TLD:N/CVSS:M/EX:H/MOD:H/AVL:L/T:H/DT:M",
                "confidence_score": 8.1,
                "confidence_score_rating": "High",
                "confidence_score_vector": "COSIS:1.0/SR:H/POP:N/TLD:N/CP:T",
                "extended": {
                    "cyberint_guid": "00000000000000000000000000000003",
                    "notes": "These domains appear to be set up for the DECOY DOG toolkit identified by Infoblox targeting enterprise infrastructure."
                }
            },
            {
                "id": "00000000-0000-0000-0000-000000000004",
                "type": "HASH",
                "hash": "000000000000000000000000000000000000000000000000000000000000001",
                "hash_type": "SHA256",
                "profile": "IID",
                "property": "MalwareC2_Azorult",
                "threat_class": "MalwareC2",
                "threat_level": 100,
                "expiration": "2025-08-25T20:00:34.12Z",
                "detected": "2024-08-25T20:00:34.12Z",
                "received": "2024-08-25T20:01:35.75Z",
                "imported": "2024-08-25T20:01:35.75Z",
                "up": "true",
                "confidence": 100,
                "batch_id": "00000000-0000-0000-0000-000000000005",
                "extended": {
                    "cyberint_guid": "00000000000000000000000000000005",
                    "notes": "The file uses Living off the Land (LotL) methods, a malicious technique that involves abusing pre-built software on the victim's machine to execute attacks. This binary matches the threat signature",
                    "sample_sha256": "000000000000000000000000000000000000000000000000000000000000001"
                }
            },
            {
                "id": "00000000-0000-0000-0000-000000000005",
                "type": "URL",
                "ip": "0.0.0.0",
                "url": "http://0.0.0.0/example",
                "profile": "IID",
                "property": "MalwareC2_Generic",
                "threat_class": "MalwareC2",
                "threat_level": 100,
                "expiration": "2025-10-01T10:32:58.891Z",
                "detected": "2025-06-03T10:32:58.891Z",
                "received": "2025-06-03T10:36:37.283Z",
                "imported": "2025-06-03T10:36:37.283Z",
                "up": "true",
                "confidence": 100,
                "batch_id": "00000000-0000-0000-0000-000000000005",
                "extended": {
                    "attack_chain": "ACIS",
                    "cyberint_guid": "00000000000000000000000000000005",
                    "notes": "Exfiltration target of Cuckoo Stealer for MacOS (SHA256: 00000000000000000000000000000005)",
                    "protocol": "http",
                    "references": "https://www.example.com/gui/file/00000000000000000000000000000005"
                }
            }
        ]
    }
}

Human Readable Output

Infoblox TIDE Indicators: Found 5 indicators between 2023-01-01T00:00:00.000Z and 2024-12-31T23:59:59.999Z

Type Value Threat Class Confidence Threat Level Expiration Property Profile
Email test@example.com APT 100 80 2043-01-06T00:41:57.421Z APT_testC2 IID
IP 0.0.0.0 APT 100 100 2042-11-01T09:29:18.721Z APT_testC2 IID
Domain test.net MalwareC2 100 100 2026-04-15T23:54:58.665Z MalwareC2_testRAT IID
File 000000000000000000000000000000000000000000000000000000000000001 MalwareC2 100 100 2025-08-25T20:00:34.12Z MalwareC2_Azorult IID
URL http://0.0.0.0/example MalwareC2 100 100 2025-10-01T10:32:58.891Z MalwareC2_Generic IID

Configuration parameters

  • api_key — (required)
  • feed — Fetch indicators
  • indicator_types — Indicator Types
  • first_fetch — First Fetch Time
  • max_fetch — Max Indicators Per Fetch
  • dga_threat — DGA Threat
  • threat_classes — Threat Classes
  • data_provider_profiles — Data Providers
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • tlp_color — Traffic Light Protocol Color
  • feedTags — Tags
  • feedBypassExclusionList — Bypass exclusion list
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedIncremental — Incremental Feed
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • infoblox-cloud-get-indicators

    Fetches a given limit of indicators from the Infoblox platform and displays them in human-readable format in the war room.

"""Base Integration for Cortex XSOAR - Unit Tests file

Pytest Unit Tests: all funcion names must start with "test_"

More details: https://xsoar.pan.dev/docs/integrations/unit-testing

You must add at least a Unit Test function for every XSOAR command
you are implementing with your integration
"""

import json
from pathlib import Path

import pytest
from FeedInfobloxThreatIntelligence import *

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()


@pytest.fixture
def client():
    """Create a real client for testing with requests_mock"""
    return Client(api_key="test_api_key", verify=False)


@pytest.fixture
def sample_tide_response():
    """Load sample TIDE response data"""
    return util_load_json("infoblox-cloud-get-indicators-response.json")


@pytest.fixture
def sample_params():
    """Sample integration parameters"""
    return {
        "feedTags": ["test-tag"],
        "tlp_color": "AMBER",
        "indicator_types": ["IP", "HOST", "EMAIL"],
        "dga_threat": "false",
        "threat_classes": ["APT", "MalwareC2"],
        "profiles": ["IID"],
    }


@pytest.fixture
def mock_demisto_methods(mocker):
    """Mock demisto methods used by the integration"""
    mock_debug = mocker.patch.object(demisto, "debug")
    mock_params = mocker.patch.object(demisto, "params")
    return {"debug": mock_debug, "params": mock_params}


class TestFetchIndicatorsCommand:
    """Test cases for fetch_indicators_command function"""

    def test_fetch_indicators_with_last_run(self, client, requests_mock):
        """Test fetch_indicators_command with existing last_run data"""
        # Test with existing last_run
        last_run = {"last_fetch_time": "2023-01-10T00:00:00.000Z"}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/host/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-host-response.json"),
        )

        indicators, next_run = fetch_indicators_command(client, {"indicator_types": "ip, host"}, last_run)

        # Verify indicators were created
        assert len(indicators) == 2
        assert isinstance(indicators, list)

        # Verify next_run contains updated last_fetch_time
        assert "last_fetch_time" in next_run
        assert isinstance(next_run["last_fetch_time"], str)

    def test_fetch_indicators_with_custom_limit(self, client, requests_mock):
        """Test fetch_indicators_command with custom max_fetch limit"""
        params = {"max_fetch": "51", "feedTags": ["test-tag"], "tlp_color": "GREEN"}
        last_run = {}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/url/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-url-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/email/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-email-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/hash/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-hash-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/host/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-host-response.json"),
        )

        indicators, next_run = fetch_indicators_command(client, params, last_run)

        # Verify API was called with custom limit
        assert requests_mock.call_count == 5
        assert "rlimit=10" in requests_mock.request_history[0].url
        assert "rlimit=11" in requests_mock.request_history[-1].url
        assert len(indicators) == 5
        assert "last_fetch_time" in next_run

    def test_fetch_indicators_api_error(self, client, requests_mock):
        """Test fetch_indicators_command with API error"""
        # Mock API error
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=500, text="Internal Server Error")

        # Should raise DemistoException
        with pytest.raises(Exception):
            fetch_indicators_command(client, {}, {})

    def test_fetch_indicators_empty_response(self, client, requests_mock):
        """Test fetch_indicators_command with empty API response"""
        # Mock empty response
        empty_response = []
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=empty_response)
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/url/hourly", json=empty_response)
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/email/hourly", json=empty_response)
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/hash/hourly", json=empty_response)
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/host/hourly", json=empty_response)

        indicators, next_run = fetch_indicators_command(client, {}, {})

        # Should return empty list
        assert len(indicators) == 0
        assert isinstance(indicators, list)

        # Should still have next_run with timestamp
        assert "last_fetch_time" in next_run

    def test_get_indicators_command_invalid_max_fetch(self, client):
        """Test infoblox_get_indicators_command with invalid max_fetch"""
        args = {"max_fetch": "invalid"}

        with pytest.raises(DemistoException) as e:
            fetch_indicators_command(client, args, {})
        assert "Parameter 'max_fetch' must be a valid integer" in str(e.value)

    def test_fetch_indicators_invalid_last_fetch_time(self, client, requests_mock):
        """Test fetch_indicators_command with invalid last_fetch_time format"""
        last_run = {"last_fetch_time": "invalid_date_format"}

        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/url/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-url-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/email/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-email-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/hash/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-hash-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/host/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-host-response.json"),
        )

        indicators, next_run = fetch_indicators_command(client, {"indicator_types": "host,ip,email,url,hash"}, last_run)

        # Should handle invalid date gracefully and use default
        assert len(indicators) == 5
        assert "last_fetch_time" in next_run

    def test_fetch_indicators_with_negative_max_fetch(self, client):
        """Test fetch_indicators_command with invalid max_fetch"""
        params = {"max_fetch": -1, "indicator_types": "host,ip"}

        with pytest.raises(DemistoException):
            fetch_indicators_command(client, params, {})

    def test_fetch_indicators_invalid_indicators_skipped(self, client, requests_mock):
        """Test fetch_indicators_command with missing required fields"""

        invalid_response = util_load_json("infoblox-cloud-fetch-indicators-invalid.json").get("threat", [])
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=invalid_response)

        indicators, next_run = fetch_indicators_command(client, {"indicator_types": "ip"}, {})

        assert len(indicators) == 0

    def test_fetch_indicators_through_main_function(self, mocker, requests_mock):
        """Test fetch_indicators_command through main function"""
        mock_indicators = mocker.patch.object(demisto, "createIndicators")

        mocker.patch.object(demisto, "command", return_value="fetch-indicators")

        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/url/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-url-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/email/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-email-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/hash/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-hash-response.json"),
        )
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/host/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-host-response.json"),
        )
        mocker.patch.object(
            demisto,
            "params",
            return_value={
                "api_key": {"password": "test-api-key"},
                "max_fetch": "10",
                "indicator_types": "host,ip,url,email,hash",
                "first_fetch": "1 hour",
            },
        )

        main()

        assert mock_indicators.called
        indicators = mock_indicators.call_args[0][0]
        assert len(indicators) == 5

    def test_get_first_fetch_time_empty_input(self):
        """Test get_first_fetch_time with empty input"""
        result = get_first_fetch_time("")
        # Should use default 1 hour
        assert "T" in result
        assert "Z" in result

    def test_get_first_fetch_time_invalid_parse(self, mocker):
        """Test get_first_fetch_time with invalid parse fallback"""
        # Mock dateparser.parse to return None for first call, valid for second
        mock_parse = mocker.patch("dateparser.parse")
        mock_parse.side_effect = [None, mocker.MagicMock(strftime=mocker.MagicMock(return_value="2023-01-01T00:00:00.000Z"))]

        result = get_first_fetch_time("invalid_date_string")
        assert result == "2023-01-01T00:00:00.000Z"

    def test_extract_indicator_fields_bool_conversion(self):
        """Test extract_indicator_fields with boolean field conversion"""

        indicator_data = {"profile": "True", "class": "false"}

        fields = extract_indicator_fields(indicator_data)

        # Should convert string to boolean
        assert fields["service"] == "True"
        assert fields["category"] == "false"


class TestModuleCommand:
    """Test cases for test_module function"""

    def test_module_success_feed_mode(self, client, mock_demisto_methods, mocker):
        """Test test_module function in feed mode (calls fetch_indicators_command)"""
        # Configure demisto mocks
        mock_demisto_methods["params"].return_value = {"feed": True}

        # Mock fetch_indicators_command
        mock_fetch = mocker.patch("FeedInfobloxThreatIntelligence.fetch_indicators_command")
        mock_fetch.return_value = ([{"type": "IP", "value": "1.2.3.4"}], {"last_fetch_time": "2023-01-01T00:00:00.000Z"})

        result = command_test_module(client)

        # Should return "ok" for successful fetch
        assert result == "ok"

        # Verify fetch_indicators_command was called
        mock_fetch.assert_called_once()

    def test_module_api_error_non_feed_mode(self, client, requests_mock, mock_demisto_methods):
        """Test test_module function with API error in non-feed mode"""
        # Mock empty API response
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=None)

        # Configure demisto mocks
        mock_demisto_methods["params"].return_value = {"feed": False}

        # Should raise exception for invalid response
        with pytest.raises(Exception):
            command_test_module(client)

    def test_module_feed_mode_error(self, client, mock_demisto_methods, mocker):
        """Test test_module function in feed mode when fetch_indicators_command raises error"""
        from CommonServerPython import DemistoException

        # Configure demisto mocks
        mock_demisto_methods["params"].return_value = {"feed": True}

        # Mock fetch_indicators_command
        mock_fetch = mocker.patch("FeedInfobloxThreatIntelligence.fetch_indicators_command")
        mock_fetch.side_effect = DemistoException("API Error")

        # Should raise DemistoException from fetch_indicators_command
        with pytest.raises(DemistoException):
            command_test_module(client)

    def test_fetch_indicators_test_mode(self, client, requests_mock):
        """Test fetch_indicators_command in test mode"""
        last_run = {}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        indicators, next_run = fetch_indicators_command(client, {"indicator_types": "ip"}, last_run, is_test=True)

        # In test mode, should return empty results
        assert indicators == []
        assert next_run == {}

        # Verify API was called with limit=1 for test
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=1" in request.url

    def test_module_authentication_error_401(self, client, requests_mock, mock_demisto_methods):
        """Test test_module with 401 authentication error"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=401)
        mock_demisto_methods["params"].return_value = {"feed": False}

        with pytest.raises(DemistoException) as e:
            command_test_module(client)
        assert "Authentication failed" in str(e.value)

    def test_module_not_found_error_404(self, client, requests_mock, mock_demisto_methods):
        """Test test_module with 404 not found error"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=404)
        mock_demisto_methods["params"].return_value = {"feed": False}

        with pytest.raises(DemistoException) as e:
            command_test_module(client)
        assert "API endpoint not found" in str(e.value)

    def test_module_failure(self, client, requests_mock, mock_demisto_methods):
        """Test test_module with failure"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", exc=requests.ConnectionError)
        mock_demisto_methods["params"].return_value = {"feed": False}

        with pytest.raises(DemistoException) as e:
            command_test_module(client)
        assert "Failed to execute test-module." in str(e.value)

    def test_main_missing_api_key(self, mocker):
        """Test main function with missing API key"""
        mocker.patch.object(demisto, "params", return_value={"api_key": {}})

        with pytest.raises(DemistoException) as e:
            main()
        assert "API Key must be provided." in str(e.value)

    def test_main_unknown_command_exception(self, mocker):
        """Test main function with unknown command"""
        mocker.patch.object(demisto, "params", return_value={"api_key": {"password": "test-key"}})
        mocker.patch.object(demisto, "command", return_value="unknown-command")
        mock_error = mocker.patch.object(demisto, "error")
        mock_return_error = mocker.patch("FeedInfobloxThreatIntelligence.return_error")

        # The main function catches all exceptions, so we expect it to handle the NotImplementedError
        main()

        # Should log error and call return_error due to exception handling
        mock_error.assert_called_with("Error in unknown-command command: Command unknown-command is not implemented")
        mock_return_error.assert_called_with(
            "Failed to execute unknown-command command.\nError:\nCommand unknown-command is not implemented"
        )

    def test_main_exception_logging(self, mocker):
        """Test main function exception logging"""
        mocker.patch.object(demisto, "params", return_value={"api_key": {"password": "test-key"}})
        mocker.patch.object(demisto, "command", return_value="test-module")
        # Mock command_test_module to raise an exception
        mocker.patch("FeedInfobloxThreatIntelligence.command_test_module", side_effect=Exception("Test error"))
        mock_error = mocker.patch.object(demisto, "error")
        mock_return_error = mocker.patch("FeedInfobloxThreatIntelligence.return_error")

        main()

        # Should log error and call return_error
        mock_error.assert_called_with("Error in test-module command: Test error")
        mock_return_error.assert_called_with("Failed to execute test-module command.\nError:\nTest error")

    def test_module_feed_fetch_interval_too_large(self, client, mock_demisto_methods):
        """Test test_module with feed fetch interval greater than 4 hours"""
        # Configure demisto mocks with feed fetch interval > 240 minutes
        mock_demisto_methods["params"].return_value = {
            "feed": True,
            "feedFetchInterval": 300,  # 5 hours > 4 hours limit
            "first_fetch": "1 hour",
        }

        # Should raise DemistoException for feed fetch interval > 4 hours
        with pytest.raises(DemistoException) as e:
            command_test_module(client)
        assert "Feed fetch interval cannot be greater than 4 hours." in str(e.value)

    def test_module_first_fetch_time_too_old(self, client, mock_demisto_methods, mocker):
        """Test test_module with first fetch time older than 4 hours"""

        # Configure demisto mocks with old first_fetch time
        mock_demisto_methods["params"].return_value = {
            "feed": True,
            "feedFetchInterval": 60,  # Valid interval
            "first_fetch": "10 hours",  # Older than 4 hours
        }

        # Should raise DemistoException for first fetch time older than 4 hours
        with pytest.raises(DemistoException) as e:
            command_test_module(client)
        assert "First fetch time cannot be older than 4 hours." in str(e.value)


class TestInfobloxGetIndicatorsCommand:
    """Test cases for infoblox_get_indicators_command function"""

    def test_get_indicators_command_success(self, client, requests_mock):
        """Test infoblox_get_indicators_command successful execution"""
        args = {
            "limit": "10",
            "indicator_types": ["IP"],
            "from_date": "2023-11-01T00:00:00.000Z",
            "to_date": "2023-11-02T00:00:00.000Z",
        }

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        result = infoblox_get_indicators_command(client, args, {})

        # Verify command result structure
        assert result.outputs_prefix == "Infoblox.FeedIndicator"
        assert result.outputs_key_field == "id"
        assert result.readable_output == util_load_text_data("infoblox-cloud-get-indicators-readable.md")

        # Verify indicators were processed
        assert len(result.outputs) == 1

        # Verify API was called with correct parameters
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=10" in request.url
        assert "from_date=2023-11-01T00%3A00%3A00.000" in request.url
        assert "to_date=2023-11-02T00%3A00%3A00.000" in request.url
        assert "include_ipv6=True" in request.url

    def test_get_indicators_limit_exceed(self, client, requests_mock):
        """Test infoblox_get_indicators_command successful execution"""
        args = {
            "limit": "100000",
            "indicator_types": ["IP"],
        }

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        infoblox_get_indicators_command(client, args, {})

        # Verify API was called with correct parameters
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=50000" in request.url

    def test_get_indicators_limit_less_than_indicator_types(self, client):
        """Test infoblox_get_indicators_command with limit less than indicator types"""
        args = {
            "limit": "1",
            "indicator_types": ["IP", "Domain"],
        }

        with pytest.raises(ValueError) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Limit must be greater than or equal to the number of indicator types." in str(e.value)

    def test_get_indicators_limit_less_than_5(self, client):
        """Test infoblox_get_indicators_command with limit less than 5"""
        args = {
            "limit": "1",
        }

        with pytest.raises(ValueError) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Please provide indicator types when limit is less than 5." in str(e.value)

    def test_get_indicators_command_with_filters(self, client, requests_mock):
        """Test infoblox_get_indicators_command with various filters"""
        args = {"limit": "5", "indicator_types": ["IP"], "dga_threat": "true", "threat_classes": ["APT", "MalwareC2"]}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        infoblox_get_indicators_command(client, args, {})

        # Verify API was called with filters
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=5" in request.url
        assert "dga=True" in request.url
        assert "class=APT%2CMalwareC2" in request.url

    def test_get_indicators_command_auto_limit_distribution(self, client, requests_mock):
        """Test infoblox_get_indicators_command with automatic limit distribution"""
        # Test with no limit parameters - should auto-populate
        args = {"indicator_types": "ip, host"}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/host/hourly",
            json=util_load_json("infoblox-cloud-fetch-indicators-host-response.json"),
        )

        infoblox_get_indicators_command(client, args, {})

        # Verify API was called with auto-populated dates
        assert requests_mock.called
        request_ip = requests_mock.request_history[0]
        assert "rlimit=5" in request_ip.url
        request_host = requests_mock.request_history[1]
        assert "rlimit=5" in request_host.url

    def test_get_indicators_command_with_date_range(self, client, requests_mock):
        """Test infoblox_get_indicators_command with date range"""
        args = {"from_date": "2023-01-01T00:00:00.000Z", "to_date": "2023-01-31T23:59:59.999Z", "indicator_types": "ip"}

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        infoblox_get_indicators_command(client, args, {})

        # Verify API was called with date parameters
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "from_date=2023-01-01T00%3A00%3A00.000" in request.url
        assert "to_date=2023-01-31T23%3A59%3A59.999" in request.url

    def test_get_indicators_command_no_args(self, client, requests_mock):
        """Test infoblox_get_indicators_command with no arguments"""

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        result = infoblox_get_indicators_command(client, {"indicator_types": "ip"}, {})

        # Verify command executed successfully with defaults
        assert len(result.outputs) == 1

        # Verify API was called
        assert requests_mock.called

    def test_get_indicators_command_empty_response(self, client, requests_mock):
        """Test infoblox_get_indicators_command with empty API response"""
        args = {"limit": "10", "indicator_types": "ip"}

        # Mock empty response
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", json={"threat": []})

        result = infoblox_get_indicators_command(client, args, {})

        assert "No indicators found." in result.readable_output
        assert requests_mock.called

    def test_get_indicators_command_invalid_limit(self, client):
        """Test infoblox_get_indicators_command with invalid limit"""
        args = {"limit": "invalid"}

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Parameter 'limit' must be a valid integer" in str(e.value)

    def test_get_indicators_only_from_date(self, client, requests_mock):
        """Test infoblox_get_indicators_command with only from_date"""
        args = {"from_date": "2023-01-01T00:00:00.000Z", "indicator_types": "ip"}

        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        infoblox_get_indicators_command(client, args, {})

        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "from_date=2023-01-01T00%3A00%3A00.000" in request.url

    def test_get_indicators_with_data_provider_profiles(self, client, requests_mock):
        """Test infoblox_get_indicators_command with data provider profiles"""
        args = {"data_provider_profiles": ["IID", "OSINT", "Custom"], "indicator_types": "ip"}

        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        infoblox_get_indicators_command(client, args, {})

        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "profile=IID%2COSINT%2CCustom" in request.url

    def test_get_indicators_threat_field_not_list(self, client, requests_mock):
        """Test infoblox_get_indicators_command when threat field is not a list"""
        invalid_response = {"threat": {"not": "a_list"}}
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/host/hourly", json=invalid_response)

        result = infoblox_get_indicators_command(client, {"indicator_types": "host"}, {})

        # Should handle invalid threat field gracefully
        assert "No indicators found." in result.readable_output

    def test_get_indicators_with_all_params(self, client, requests_mock):
        """Test get_indicators with all parameters"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", json={"threat": []})

        client.get_indicators(
            limit=100,
            indicator_types=["IP"],
            from_date="2023-01-01T00:00:00.000Z",
            to_date="2023-01-02T00:00:00.000Z",
            dga_flag="true",
            threat_class=["APT", "MalwareC2"],
            profile=["IID", "OSINT"],
        )

        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=100" in request.url
        assert "from_date=2023-01-01T00%3A00%3A00.000Z" in request.url
        assert "to_date=2023-01-02T00%3A00%3A00.000Z" in request.url
        assert "dga=true" in request.url
        assert "class=APT%2CMalwareC2" in request.url
        assert "profile=IID%2COSINT" in request.url

    def test_get_indicators_command_401_authentication_error(self, client, requests_mock):
        """Test infoblox-cloud-get-indicators command with 401 Authentication Error"""
        args = {"limit": "10"}

        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=401)

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Authentication Error (401): API key is invalid or expired." in str(e.value)

    def test_get_indicators_command_403_forbidden_error(self, client, requests_mock):
        """Test infoblox-cloud-get-indicators command with 403 Forbidden Error"""
        args = {"limit": "10"}

        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=403)

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Forbidden (403): Insufficient permissions to access this resource." in str(e.value)

    def test_get_indicators_command_404_not_found_error(self, client, requests_mock):
        """Test infoblox-cloud-get-indicators command with 404 Not Found Error"""
        args = {"limit": "10"}

        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=404)

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Not Found (404): The requested resource was not found." in str(e.value)

    def test_get_indicators_command_429_rate_limit_error(self, client, requests_mock):
        """Test infoblox-cloud-get-indicators command with 429 Rate Limit Error"""
        args = {"limit": "10"}

        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=429)

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Rate Limit Exceeded (429): Too many requests. Please try again later." in str(e.value)

    def test_get_indicators_command_500_server_error(self, client, requests_mock):
        """Test infoblox-cloud-get-indicators command with 500 Internal Server Error"""
        args = {"limit": "10"}

        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=500)

        with pytest.raises(DemistoException) as e:
            infoblox_get_indicators_command(client, args, {})
        assert "Server Error (500): Internal server error occurred. Please try again later." in str(e.value)

    def test_handle_error_response_400_simple(self, client, requests_mock):
        """Test _handle_error_response with 400 status code"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=400)

        with pytest.raises(DemistoException) as e:
            client.get_indicators()
        assert "Bad Request (400): Invalid parameters or request body." in str(e.value)

    def test_handle_error_response_unknown_status(self, client, requests_mock):
        """Test _handle_error_response with unknown status code"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", status_code=418)

        with pytest.raises(DemistoException) as e:
            client.get_indicators()
        assert "Error in API call with status code 418" in str(e.value)

    def test_http_request_timeout_error(self, client, requests_mock):
        """Test http_request with timeout error"""
        requests_mock.get(f"{BASE_URL}/tide/api/data/threats/ip/hourly", exc=DemistoException("Read timed out"))

        with pytest.raises(DemistoException) as e:
            client.get_indicators()
        assert "Connection timed out. Check your internet connection" in str(e.value)

    def test_get_indicators_command_through_main_function(self, mocker, requests_mock):
        """Test infoblox_get_indicators_command through main function"""
        # Mock demisto functions
        mock_return_results = mocker.patch("FeedInfobloxThreatIntelligence.return_results")
        mocker.patch.object(demisto, "command", return_value="infoblox-cloud-get-indicators")
        mocker.patch.object(
            demisto,
            "args",
            return_value={
                "limit": "5",
                "indicator_types": "ip",
                "from_date": "2023-01-01T00:00:00.000Z",
                "to_date": "2023-01-02T00:00:00.000Z",
            },
        )
        mocker.patch.object(demisto, "params", return_value={"api_key": {"password": "test-api-key"}})

        # Mock the API endpoint
        requests_mock.get(
            f"{BASE_URL}/tide/api/data/threats/ip/hourly", json=util_load_json("infoblox-cloud-fetch-indicators-ip-response.json")
        )

        # Call main function
        main()

        # Verify return_results was called
        assert mock_return_results.called
        result = mock_return_results.call_args[0][0]

        # Verify the command result structure
        assert result.outputs_prefix == "Infoblox.FeedIndicator"
        assert result.outputs_key_field == "id"
        assert len(result.outputs) == 1

        # Verify API was called with correct parameters
        assert requests_mock.called
        request = requests_mock.request_history[0]
        assert "rlimit=5" in request.url


class TestHelperFunctions:
    """Test cases for helper functions"""

    def test_map_indicator_type_ip(self):
        """Test map_indicator_type for IP indicators"""
        indicator_data = {"type": "IP", "ip": "1.2.3.4"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "IP"
        assert value == "1.2.3.4"

    def test_map_indicator_type_host(self):
        """Test map_indicator_type for HOST indicators"""
        indicator_data = {"type": "HOST", "host": "example.com"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "Domain"
        assert value == "example.com"

    def test_map_indicator_type_email(self):
        """Test map_indicator_type for EMAIL indicators"""
        indicator_data = {"type": "EMAIL", "email": "test@example.com"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "Email"
        assert value == "test@example.com"

    def test_map_indicator_type_hash(self):
        """Test map_indicator_type for HASH indicators"""
        indicator_data = {"type": "HASH", "hash": "abc123", "hash_type": "SHA256"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "File"
        assert value == "abc123"

    def test_map_indicator_type_url(self):
        """Test map_indicator_type for URL indicators"""
        indicator_data = {"type": "URL", "url": "http://example.com/path"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "URL"
        assert value == "http://example.com/path"

    def test_extract_indicator_fields(self, sample_tide_response):
        """Test extract_indicator_fields function"""
        indicator_data = sample_tide_response["threat"][0]  # EMAIL indicator
        feed_tags = ["test-tag"]
        tlp_color = "AMBER"

        fields = extract_indicator_fields(indicator_data, feed_tags, tlp_color)

        # Verify required fields are present
        assert "tags" in fields
        assert "trafficlightprotocol" in fields
        assert "confidence" in fields
        assert "sourcepriority" in fields

        # Verify values
        assert fields["tags"] == ["test-tag"]
        assert fields["trafficlightprotocol"] == "AMBER"
        assert fields["confidence"] == 100

    def test_calculate_dbot_score_high_threat(self):
        """Test calculate_dbot_score for high threat level"""
        indicator_data = {"threat_level": 100}
        score = calculate_dbot_score(indicator_data)
        assert score == 3  # Bad

    def test_calculate_dbot_score_medium_threat(self):
        """Test calculate_dbot_score for medium threat level"""
        indicator_data = {"threat_level": 60}
        score = calculate_dbot_score(indicator_data)
        assert score == 2  # Suspicious

    def test_calculate_dbot_score_low_threat(self):
        """Test calculate_dbot_score for low threat level"""
        indicator_data = {"threat_level": 10}
        score = calculate_dbot_score(indicator_data)
        assert score == 1  # Good

    def test_calculate_dbot_score_no_threat(self):
        """Test calculate_dbot_score for no threat level"""
        indicator_data = {}
        score = calculate_dbot_score(indicator_data)
        assert score == 0  # Unknown

    def test_calculate_dbot_score_high_confidence(self):
        """Test calculate_dbot_score for high confidence"""
        indicator_data = {"threat_level": 50}
        score = calculate_dbot_score(indicator_data)
        assert score == 2

    def test_calculate_dbot_score_medium_confidence(self):
        """Test calculate_dbot_score for medium confidence"""
        indicator_data = {"threat_level": 30}
        score = calculate_dbot_score(indicator_data)
        assert score == 2  # Suspicious due to medium threat level

    def test_map_indicator_type_invalid_data(self):
        """Test map_indicator_type with invalid data type"""
        indicator_data = "invalid_string"
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "Domain"  # Default fallback
        assert value is None

    def test_map_indicator_type_unknown_type(self):
        """Test map_indicator_type with unknown indicator type"""
        indicator_data = {"type": "UNKNOWN", "host": "test.com"}
        xsoar_type, value = map_indicator_type(indicator_data)
        assert xsoar_type == "Domain"  # Default fallback
        assert value == "test.com"

    def test_extract_indicator_fields_nested_fields(self):
        """Test extract_indicator_fields with nested field navigation"""
        indicator_data = {
            "extended": {"notes": "Test description"},
            "confidence": 85,
            "threat_level": 75,
            "class": "MalwareC2",
            "property": "TestMalware",
        }
        feed_tags = ["nested-test"]
        tlp_color = "RED"

        fields = extract_indicator_fields(indicator_data, feed_tags, tlp_color)

        assert fields["description"] == "Test description"
        assert fields["confidence"] == 85
        assert fields["sourcepriority"] == 75
        assert fields["category"] == "MalwareC2"
        assert fields["malwarefamily"] == "TestMalware"
        assert fields["tags"] == ["nested-test"]
        assert fields["trafficlightprotocol"] == "RED"

    def test_extract_indicator_fields_type_conversions(self):
        """Test extract_indicator_fields with different type conversions"""
        indicator_data = {
            "threat_level": "90",  # String that should convert to int
            "up": "true",  # String that should convert to bool
            "confidence": 95.5,  # Float that should stay as is
            "received": "2023-01-01T00:00:00Z",  # Date field
        }

        fields = extract_indicator_fields(indicator_data)

        assert fields["sourcepriority"] == 90  # Converted to int
        assert fields["state"] == "true"  # Bool conversion (stored as string in this implementation)
        assert fields["confidence"] == 95.5
        assert fields["lastseenbysource"] == "2023-01-01T00:00:00Z"

    def test_extract_indicator_fields_invalid_int_conversion(self):
        """Test extract_indicator_fields with invalid int conversion"""
        indicator_data = {"threat_level": "invalid_number"}

        fields = extract_indicator_fields(indicator_data)

        # Should not include the field if conversion fails
        assert "sourcepriority" not in fields

    def test_extract_indicator_fields_missing_nested_path(self):
        """Test extract_indicator_fields with missing nested path"""
        indicator_data = {
            "extended": {}  # Missing notes field
        }

        fields = extract_indicator_fields(indicator_data)

        # Should not include description if nested path doesn't exist
        assert "description" not in fields

    def test_extract_indicator_fields_default_values(self):
        """Test extract_indicator_fields with default values"""
        indicator_data = {}

        fields = extract_indicator_fields(indicator_data)

        # Should include default TLP value
        assert fields["trafficlightprotocol"] == "AMBER"

    def test_validate_str_param_none_required(self):
        """Test validate_str_param with None parameter when required"""
        with pytest.raises(DemistoException) as e:
            validate_str_param(None, "test_param", required=True)
        assert "Missing required parameter 'test_param'" in str(e.value)

    def test_validate_str_param_unconvertible_object(self):
        """Test validate_str_param with object that cannot be converted to string"""

        class UnconvertibleObject:
            def __str__(self):
                raise ValueError("Cannot convert to string")

        with pytest.raises(DemistoException) as e:
            validate_str_param(UnconvertibleObject(), "test_param")
        assert "Parameter 'test_param' must be a string or convertible to string" in str(e.value)

    def test_validate_str_param_empty_string_required(self):
        """Test validate_str_param with empty string when required"""
        with pytest.raises(DemistoException) as e:
            validate_str_param("", "test_param", required=True)
        assert "Parameter 'test_param' cannot be empty" in str(e.value)

    def test_validate_int_param_none_required_exception(self):
        """Test validate_int_param with None parameter when required raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param(None, "test_param", required=True)
        assert "Missing required parameter 'test_param'" in str(e.value)

    def test_validate_int_param_empty_string_required_exception(self):
        """Test validate_int_param with empty string when required raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param("", "test_param", required=True)
        assert "Parameter 'test_param' cannot be empty" in str(e.value)

    def test_validate_int_param_list_type_exception(self):
        """Test validate_int_param with list type raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param([1, 2, 3], "test_param")
        assert "Cannot convert list to int" in str(e.value)

    def test_validate_int_param_invalid_string_exception(self):
        """Test validate_int_param with invalid string raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param("not_a_number", "test_param")
        assert "Parameter 'test_param' must be a valid integer" in str(e.value)

    def test_validate_int_param_min_val_exception(self):
        """Test validate_int_param with value below minimum raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param(5, "test_param", min_val=10)
        assert "Parameter 'test_param' must be at least 10" in str(e.value)

    def test_validate_int_param_max_val_exception(self):
        """Test validate_int_param with value above maximum raises exception"""
        with pytest.raises(DemistoException) as e:
            validate_int_param(100, "test_param", max_val=50)
        assert "Parameter 'test_param' must be at most 50" in str(e.value)