AbuseIPDB

Central repository to report and identify IP addresses that have been associated with malicious activity online. Check the Detailed Information section for more information on how to configure the integration.

Data Enrichment & Threat Intelligence · AbuseIPDB

Details

IDAbuseIPDB
ProviderAbuseDB
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10404775
Supported ModulesAgentix Cloud Runtime Security XSIAM EDR Cortex Cloud

README

Use the AbuseIPDB integration to report and identify IP addresses that have been associated with malicious activity online.

Use Cases

Check, Report, and get block list of top malicious IPs.

Configure AbuseIPDB on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for AbuseIPDB.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • API Key (v2).
    • Source Reliability: Reliability of the source providing the intelligence data.
    • IP Threshold. Minimum score from AbuseIPDB analysis to consider the IP malicious. (>20).
    • Max reports age.
    • Disable reputation lookups for private IP addresses: To reduce the number of lookups made to the AbuseIPDB API.
    • Disregard quota errors.
    • Abuse.ch Hunting API URL: The base URL for the hunting API (Default: https://hunting-api.abuse.ch/api/v1/).
    • Abuse.ch Hunting API Key: A credential field for the API key.
  4. Click Test to validate the API Key, and connection.

Commands

You can execute these commands from the Cortex XSOAR 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.

  1. Check if an IP address is in the AbuseIP database: ip
  2. Query a block of IP addresses: abuseipdb-check-cidr-block
  3. Report an IP address: abuseipdb-report-ip
  4. Get a list of the most reported IP addresses: abuseipdb-get-blacklist
  5. Get a list of report categories: abuseipdb-get-categories
  6. Get the full list of false positive IP addresses: abuseipdb-get-fplist

1. Check if an IP address is in the AbuseIP database


Checks the specified IP address against the AbuseIP database.

Base Command

ip

Input
Argument Name Description Required
ip IP address to check Required
days Time range to return reports for (in days), default is 30 Optional
verbose Report length, "true" returns the full report, "false" does not return reported categories, default is "true" Optional
threshold Minimum score from AbuseIPDB to consider the IP malicious (must be greater than 20), default is 80 Optional
override_private_lookup Enrichment of private IP addresses will be conducted even if it has been disabled at the integration level, default is "false" Optional

 

Context Output
Path Type Description
AbuseIPDB.IP.Address unknown IP address
AbuseIPDB.IP.AbuseConfidenceScore unknown Confidence score fetched from AbuseIPDB
AbuseIPDB.IP.TotalReports unknown The number of times this address has been reported
AbuseIPDB.IP.Geo.Country String Country associated with this IP Address
AbuseIPDB.IP.Geo.CountryCode String Country code associated with this IP Address
AbuseIPDB.IP.Hostnames String The hostame(s) of the IP address.
AbuseIPDB.IP.IpVersion String The version of the IP address.
AbuseIPDB.IP.IsPublic String Is the IP address public.
AbuseIPDB.IP.IsTor String Is the IP address a Tor IP.
AbuseIPDB.IP.IsWhitelisted String Is the IP address whitelisted.
AbuseIPDB.IP.LastReportedAt String When the IP address was last reported.
AbuseIPDB.IP.NumDistinctUsers String The distinct number of users.
AbuseIPDB.IP.Address.Reports unknown Reports summary (for "verbose" reports)
DBotScore.Score unknown Analysis score
DBotScore.Vendor unknown Vendor name (AbuseIPDB)
DBotScore.Indicator unknown The IP address
DBotScore.Type unknown The type (ip)
AbuseIPDB.IP.Malicious.Vendor unknown The vendor that determined this IP address to be malicious
AbuseIPDB.IP.Malicious.Detections unknown The Detections that led to the verdict
AbuseIPDB.IP.UsageType String Usage type of the IP.
AbuseIPDB.IP.Domain String Domain of the IP.

 

Command Example

!ip ip=8.8.8.8 days=30 verbose=true

Context Example
Human Readable Output

image

2. Query a block of IP addresses


Queries a block of IPs to check against the database

Base Command

abuseipdb-check-cidr-block

Input
Argument Name Description Required
network IPv4 Address Block in CIDR notation. Required
days Time range to return reports for (in days), default is 30 Optional
limit Maximum number of IPs to check, default is 40 Optional
threshold Minimum score from AbuseIPDB to consider the IP malicious (must be greater than 20), default is 80 Optional

 

Context Output
Path Type Description
AbuseIPDB.IP.Address unknown IP address
AbuseIPDB.IP.AbuseConfidenceScore unknown Confidence score fetched from AbuseIPDB
AbuseIPDB.IP.Geo.Country String Country associated with this IP Address
AbuseIPDB.IP.Geo.CountryCode String Country code associated with this IP Address
AbuseIPDB.IP.Hostnames String The hostame(s) of the IP address.
AbuseIPDB.IP.IpVersion String The version of the IP address.
AbuseIPDB.IP.IsPublic String Is the IP address public.
AbuseIPDB.IP.IsTor String Is the IP address a Tor IP.
AbuseIPDB.IP.IsWhitelisted String Is the IP address whitelisted.
AbuseIPDB.IP.LastReportedAt String When the IP address was last reported.
AbuseIPDB.IP.NumDistinctUsers String The distinct number of users.
AbuseIPDB.IP.TotalReports unknown The number of times this address has been reported
DBotScore.Score unknown Analysis score
DBotScore.Vendor unknown Vendor name (AbuseIPDB)
DBotScore.Indicator unknown The IP address
DBotScore.Type unknown The type (ip)
AbuseIPDB.IP.Malicious.Vendor unknown The vendor that determined this IP address to be malicious
AbuseIPDB.IP.Malicious.Detections unknown The Detections that led to the verdict
AbuseIPDB.IP.UsageType String Usage type of the IP.
AbuseIPDB.IP.Domain String Domain of the IP.

 

Command Example

!abuseipdb-check-cidr-block network="127.0.0.2/24" days="30" limit="40" threshold="80"

 

Human Readable Output

image

3. Report an IP address


Report an IP address to AbuseIPDB

Base Command

abuseipdb-report-ip

Input
Argument Name Description Required
ip The IP address to report Required
categories CSV list of category IDs (numerical representation or in their name) Required

 

Context Output

There is no context output for this command.

Command Example

!abuseipdb-report-ip ip=8.8.8.8 categories="18,22,23"

Human Readable Output

image

4. Get a list of the most reported IP addresses


Returns a list of the most reported IP addresses

Base Command

abuseipdb-get-blacklist

Input
Argument Name Description Required
days Time range to return reports for (in days), default is 30 Optional
limit Maximum number of IPs to retrieve, default is 50 Optional
confidence The Minimum confidence required for the retrieved IPs. Default is 100 Optional

 

Context Output
Path Type Description
AbuseIPDB.Blacklist unknown List of IPs on block list

 

Command Example

!abuseipdb-get-blacklist days=30 limit=5

Context Example
Human Readable Output

image

5. Get a list of report categories


Returns a list of report categories from AbuseIPDB

Base Command

abuseipdb-get-categories

Input

There are no input arguments for this command.

Context Output
Path Type Description
AbuseIPDB.Categories string List of AbuseIPDB categories

 

Command Example

!abuseipdb-get-categories

 

Human Readable Output

image

6. Get the full list of false positive IP addresses


Returns the False Positive List (FPL) from abuse.ch, containing indicators (IPs and domains) that have been removed from their blocklists.

Base Command

abuseipdb-get-fplist

Input
Argument Name Description Required
format The format of the output (json, csv). Set to 'csv' to download the response as a file. The 'json' format inserts the response into the incident context. Optional
limit The maximum number of results to return. Ignored when all_results is set to true or when format is set to csv. Optional
all_results Whether to return all results. Ignored when format is set to csv. Optional

 

Context Output
Path Type Description
AbuseIPDB.FPL.id String The unique identifier for the False Positive List entry.
AbuseIPDB.FPL.time_stamp Date The date and time (UTC) when the entry was added to the False Positive List.
AbuseIPDB.FPL.platform String The platform or service associated with the entry.
AbuseIPDB.FPL.entry_type String The type of the indicator (e.g., IPv4, domain).
AbuseIPDB.FPL.entry_value String The actual indicator value (IP address or domain) that was marked as a false positive.
AbuseIPDB.FPL.removed_by String The entity or user who requested the removal of the indicator from the blocklist.
AbuseIPDB.FPL.removal_notes String Additional context or justification for why the indicator was removed.

 

Command Example

!abuseipdb-get-fplist format="json" limit=10

 

Additional Information

  • What is the "Confidence of Abuse" rating, and how is it calculated?
    AbuseIPDB confidence of abuse is a rating (0-100) of how confident we are, based on user reports, that an IP address is completely malicious. A rating of 100 means we are certain that an IP address is malicious, and a rating of 0 means we have no reason to suspect it is malicious.

Configuration parameters

  • server — AbuseIP server URL (required)
  • credentials
  • apikey — API Key (v2)
  • abusech_hunting_url — Abuse.ch Hunting API URL
  • hunting_credentials
  • integrationReliability — Source Reliability
  • threshold — Minimum score threshold
  • days — Maximum reports age (in days)
  • disable_private_ip_lookup — Disable reputation lookups for private IP addresses
  • disregard_quota — Disregard quota errors
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (6)

  • abuseipdb-check-cidr-block

    Queries a block of IP addresses to check against the database.

  • abuseipdb-get-blacklist

    Returns a list of the most reported IP addresses.

  • abuseipdb-get-categories

    Returns a list of report categories from AbuseIPDB.

  • abuseipdb-get-fplist

    Returns the False Positive List (FPL) from abuse.ch, including IP and domain indicators removed from their blocklists.

  • abuseipdb-report-ip

    Reports an IP address to AbuseIPDB.

  • ip

    Checks the specified IP address against the AbuseIP database.

from CommonServerPython import *
import pytest

RETURN_ERROR_TARGET = "AbuseDB.return_error"


class DotDict(dict):
    """dot.notation access to dictionary attributes"""

    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__


@pytest.mark.parametrize("days", [1, 400, 0])
def test_ip_command_api_params(mocker, days):
    """
    Given: A mocked AbuseDB API that returns a successful response
          And integration params with conditional days handling (uses 50 when days=0)
    When: The check_ip_command is called with different days parameters (1, 400, 0)
    Then: The API request should be made with correct parameters including:
          - ipAddress set to the provided IP (1.1.1.1)
          - maxAgeInDays set to the exact days value passed to the function
          And the integration params should handle edge cases (days=0 → fallback to 50)
    """
    from requests import Session

    expected_res = {
        "data": {
            "ipAddress": "1.1.1.1",
            "abuseConfidencePercentage": 0,
            "abuseConfidenceScore": 0,
            "countryCode": "US",
            "countryName": "United States",
            "usageType": "dummy",
            "isp": "dummy",
            "domain": "dummy.com",
            "totalReports": 0,
            "numDistinctUsers": 0,
            "lastReportedAt": None,
            "reports": [],
            "hostnames": [],
            "ipVersion": 4,
            "isPublic": True,
            "isTor": False,
            "isWhitelisted": False,
        }
    }

    def json_func():
        return expected_res

    success_response = {"status_code": 200, "json": json_func}

    params = {
        "server": "https://api.abuseipdb.com/api/v2/",
        "proxy": True,
        "disregard_quota": True,
        "disable_private_ip_lookup": False,
        "integrationReliability": DBotScoreReliability.C,
        "days": days if days else 50,
    }

    success_response_with_dot_access = DotDict(success_response)

    mocker.patch.object(demisto, "params", return_value=params)
    request_mock = mocker.patch.object(Session, "request", return_value=success_response_with_dot_access)
    mocker.patch.object(demisto, "results")
    from AbuseDB import check_ip_command

    check_ip_command(DBotScoreReliability.C, ["1.1.1.1"], days=days, threshold=75)

    # Verify the API call was made with correct parameters
    assert request_mock.call_count == 1
    call_args = request_mock.call_args

    # Check method and URL
    assert call_args[0][0] == "GET"  # method
    assert "check" in call_args[0][1]  # URL contains 'check' endpoint

    # Check parameters sent to API
    api_params = call_args[1]["params"]
    assert api_params["ipAddress"] == "1.1.1.1"
    assert api_params["maxAgeInDays"] == days


def test_ip_command_when_api_quota_reached(mocker):
    from requests import Session

    def json_func():
        return {}

    api_quota_reached_request_response = {"status_code": 429, "json": json_func}

    params = {
        "server": "test",
        "proxy": True,
        "disregard_quota": True,
        "disable_private_ip_lookup": False,
        "integrationReliability": DBotScoreReliability.C,
    }

    api_quota_reached_request_response_with_dot_access = DotDict(api_quota_reached_request_response)

    mocker.patch.object(demisto, "params", return_value=params)
    mocker.patch.object(Session, "request", return_value=api_quota_reached_request_response_with_dot_access)
    return_error_mock = mocker.patch(RETURN_ERROR_TARGET)
    from AbuseDB import check_ip_command

    check_ip_command(DBotScoreReliability.C, ["1.1.1.1"], days=7, verbose=False, threshold=10)
    assert return_error_mock.call_count == 0


def test_get_fplist_command_json(mocker):
    """
    Given:
        - A JSON response from Abuse.ch Hunting API for get_fplist.
    When:
        - get_fplist_command is called with format='json'.
    Then:
        - Verify the flattening logic (ID injection).
        - Verify CommandResults output.
    """
    mocker.patch.object(demisto, "command", return_value="abuseipdb-get-fplist")
    from AbuseDB import get_fplist_command
    import AbuseDB

    mock_response = {
        "1001": {
            "time_stamp": "2024-01-01 12:00:00 UTC",
            "platform": "TestPlatform",
            "entry_type": "ip",
            "entry_value": "8.8.8.8",
            "removed_by": "user1",
            "removal_notes": "test note",
        },
        "1002": {
            "time_stamp": "2024-01-01 13:00:00 UTC",
            "platform": "TestPlatform",
            "entry_type": "domain",
            "entry_value": "test.com",
            "removed_by": "user2",
            "removal_notes": "another note",
        },
    }

    class MockResponse:
        def __init__(self, json_data):
            self.json_data = json_data
            self.status_code = 200

        def json(self):
            return self.json_data

    mocker.patch.object(AbuseDB, "abusech_hunting_http_request", return_value=MockResponse(mock_response))

    results = get_fplist_command(format="json", limit=1, all_results=False)

    assert isinstance(results, CommandResults)
    assert isinstance(results.outputs, list)
    assert len(results.outputs) == 1
    assert results.outputs[0]["id"] == "1001"
    assert results.outputs[0]["entry_value"] == "8.8.8.8"
    assert "Abuse.ch False Positive List" in results.readable_output


def test_get_fplist_command_csv(mocker):
    """
    Given:
        - A CSV response from Abuse.ch Hunting API for get_fplist.
    When:
        - get_fplist_command is called with format='csv'.
    Then:
        - Verify fileResult is returned with correct content.
    """
    mocker.patch.object(demisto, "command", return_value="abuseipdb-get-fplist")
    from AbuseDB import get_fplist_command
    import AbuseDB

    # Mock demisto.uniqueFile and demisto.investigation to prevent physical file creation
    # while still allowing fileResult to execute its internal logic.
    mocker.patch.object(demisto, "uniqueFile", return_value="test_file")
    mocker.patch.object(demisto, "investigation", return_value={"id": "test_inv"})

    # Mock the built-in open to prevent writing to the file system
    mocked_open = mocker.patch("builtins.open", mocker.mock_open())

    csv_content = b'"time_stamp","removal_id","platform","entry_type","entry_value","removed_by","removal_notes"'

    class MockResponse:
        def __init__(self, content):
            self.content = content
            self.status_code = 200

    mocker.patch.object(AbuseDB, "abusech_hunting_http_request", return_value=MockResponse(csv_content))

    results = get_fplist_command(format="csv", limit=10, all_results=True)

    assert results["Type"] == EntryType.FILE
    assert results["File"] == "abusech_fplist.csv"
    assert results["ContentsFormat"] == "text"

    # Verify that open was called with the expected filename and mode
    mocked_open.assert_called_once_with("test_inv_test_file", "wb")
    # Verify that the correct content was written
    mocked_open().write.assert_called_once_with(csv_content)


def test_get_fplist_command_error(mocker):
    """
    Given:
        - An API error from Abuse.ch Hunting API.
    When:
        - get_fplist_command is called.
    Then:
        - Verify Exception is raised with the expected message.
    """
    import AbuseDB
    from AbuseDB import get_fplist_command

    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(AbuseDB, "abusech_hunting_http_request", side_effect=Exception("API Error"))

    with pytest.raises(Exception, match="API Error"):
        get_fplist_command(format="json", limit=10, all_results=True)


def test_abusech_hunting_http_request_success(mocker):
    """
    Given:
        - Valid headers and payload.
        - A successful 200 OK response from the API.
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify the request is made with correct parameters.
        - Verify the response object is returned.
    """
    from requests import Session
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request

    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"query_status": None}  # No error wrapped in JSON

    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(AbuseDB, "INSECURE", False)
    request_mock = mocker.patch.object(Session, "request", return_value=mock_response)

    headers = {"Auth-Key": "test"}
    payload = {"query": "test"}

    response = abusech_hunting_http_request(headers, payload)

    assert response is not None
    assert response.status_code == 200
    request_mock.assert_called_once_with(method="POST", url="https://test-url", headers=headers, json=payload, verify=True)


def test_abusech_hunting_http_request_api_error(mocker):
    """
    Given:
        - A non-200 response from the API.
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify Exception is raised with the expected message.
    """
    from requests import Session
    import requests
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request

    mock_response = mocker.Mock()
    mock_response.status_code = 404
    # raise_for_status raises HTTPError
    mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Client Error: Not Found")

    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(Session, "request", return_value=mock_response)

    with pytest.raises(Exception, match="Failed to connect to Abuse.ch: 404 Client Error: Not Found"):
        abusech_hunting_http_request({}, {})


def test_abusech_hunting_http_request_api_wrapped_error(mocker):
    """
    Given:
        - A response where the error is wrapped in a 200 OK response.
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify Exception is raised with the error message from the 'data' field.
    """
    from requests import Session
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request

    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {
        "query_status": "unknown_auth_key",
        "data": "The Auth-Key you provided is unknown.",
    }

    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(Session, "request", return_value=mock_response)

    with pytest.raises(Exception, match="The Auth-Key you provided is unknown"):
        abusech_hunting_http_request({}, {})


def test_abusech_hunting_http_request_non_json_response(mocker):
    """
    Given:
        - A response that is not JSON (e.g. CSV).
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify the response object is returned as is.
    """
    from requests import Session
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request
    from json import JSONDecodeError

    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.json.side_effect = JSONDecodeError("Not JSON content", "CSV,Data,Points", 0)

    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(Session, "request", return_value=mock_response)

    response = abusech_hunting_http_request({}, {})
    assert response == mock_response


def test_abusech_hunting_http_request_connection_error(mocker):
    """
    Given:
        - A connection exception during the request.
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify Exception is raised with the connection error message.
    """
    from requests import Session
    import requests
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request

    mocker.patch.object(
        demisto,
        "params",
        return_value={"abusech_hunting_url": "https://test-url", "hunting_credentials": {"password": "test-key"}},
    )
    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")
    mocker.patch.object(Session, "request", side_effect=requests.exceptions.RequestException("Connection failed"))

    with pytest.raises(Exception, match="Failed to connect to Abuse.ch: Connection failed"):
        abusech_hunting_http_request({}, {})


def test_abusech_hunting_http_request_missing_url(mocker):
    """
    Given:
        - A missing ABUSECH_URL parameter.
    When:
        - abusech_hunting_http_request is called.
    Then:
        - Verify Exception is raised with the missing URL message.
    """
    import AbuseDB
    from AbuseDB import abusech_hunting_http_request

    # Patch the global variable directly to simulate missing parameter
    mocker.patch.object(AbuseDB, "ABUSECH_URL", None)
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-key")

    with pytest.raises(Exception, match="Hunting API URL was not provided"):
        abusech_hunting_http_request({}, {})


def _mock_abuseipdb_globals(mocker):
    """Patch module-level globals that are evaluated at import time."""
    import AbuseDB

    mocker.patch.object(AbuseDB, "MAX_AGE", "30")
    mocker.patch.object(AbuseDB, "VERBOSE", False)
    mocker.patch.object(AbuseDB, "THRESHOLD", "80")
    mocker.patch.object(AbuseDB, "DISABLE_PRIVATE_IP_LOOKUP", False)
    mocker.patch.object(AbuseDB, "API_KEY", "test-api-key")
    mocker.patch.object(AbuseDB, "HEADERS", {"Key": "test-api-key", "Accept": "application/json"})
    mocker.patch.object(AbuseDB, "INSECURE", False)
    mocker.patch.object(AbuseDB, "PROXY", False)
    mocker.patch.object(AbuseDB, "ABUSECH_API_KEY", "test-hunting-key")
    mocker.patch.object(AbuseDB, "ABUSECH_URL", "https://test-url")
    mocker.patch.object(
        demisto,
        "params",
        return_value={"apikey": "test", "threshold": "80", "disregard_quota": "false"},
    )
    mocker.patch.object(demisto, "results")


def _mock_http_request(mocker):
    """Mock requests.Session.request to return a valid AbuseIPDB API response."""
    from requests import Session

    api_response = {
        "data": {
            "ipAddress": "1.1.1.1",
            "abuseConfidencePercentage": 0,
            "abuseConfidenceScore": 0,
            "countryCode": "US",
            "countryName": "United States",
            "usageType": "dummy",
            "isp": "dummy",
            "domain": "dummy.com",
            "totalReports": 0,
            "numDistinctUsers": 0,
            "lastReportedAt": None,
            "reports": [],
            "hostnames": [],
            "ipVersion": 4,
            "isPublic": True,
            "isTor": False,
            "isWhitelisted": False,
            "reportedAddress": [
                {
                    "ipAddress": "192.168.1.1",
                    "abuseConfidenceScore": 0,
                    "countryCode": "US",
                    "countryName": "United States",
                    "totalReports": 0,
                    "numReports": 0,
                    "lastReportedAt": None,
                }
            ],
        }
    }

    def json_func():
        return api_response

    mocker.patch.object(
        Session,
        "request",
        return_value=DotDict({"status_code": 200, "json": json_func}),
    )


def _mock_abusech_http_request(mocker):
    """Mock abusech_hunting_http_request to return a valid FPL response."""
    import AbuseDB

    mock_fplist_response = {
        "1001": {
            "time_stamp": "2024-01-01 12:00:00 UTC",
            "platform": "TestPlatform",
            "entry_type": "ip",
            "entry_value": "8.8.8.8",
            "removed_by": "user1",
            "removal_notes": "test note",
        }
    }

    class MockFPResponse:
        status_code = 200

        def json(self):
            return mock_fplist_response

    mocker.patch.object(AbuseDB, "abusech_hunting_http_request", return_value=MockFPResponse())


@pytest.mark.parametrize(
    "command_name, func_name, required_args, extra_args",
    [
        pytest.param(
            "ip",
            "check_ip_command",
            {"ip": "1.1.1.1"},
            {"nonexistent_param": "value"},
            id="ip-required-args-only",
        ),
        pytest.param(
            "abuseipdb-check-cidr-block",
            "check_block_command",
            {"network": "192.168.1.0/24", "limit": "40"},
            {"nonexistent_param": "value"},
            id="check-cidr-block-required-args-only",
        ),
    ],
)
class TestReliabilityCommandsWithHttpRequest:
    """Tests for commands that take ``reliability`` as a positional arg and use the AbuseIPDB HTTP API."""

    def test_required_args_only(self, mocker, command_name, func_name, required_args, extra_args):
        """Test that the command works with only required arguments."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_http_request(mocker)

        func = getattr(AbuseDB, func_name)
        func(DBotScoreReliability.B, **required_args)

    def test_required_args_with_extra(self, mocker, command_name, func_name, required_args, extra_args):
        """Test that the command tolerates extra kwargs from ``**demisto.args()``."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_http_request(mocker)

        func = getattr(AbuseDB, func_name)
        args = {**required_args, **extra_args}
        func(DBotScoreReliability.B, **args)


@pytest.mark.parametrize(
    "command_name, func_name, required_args, extra_args",
    [
        pytest.param(
            "abuseipdb-report-ip",
            "report_ip_command",
            {"ip": "1.2.3.4", "categories": "Hacking"},
            {"nonexistent_param": "value"},
            id="report-ip-required-args-only",
        ),
        pytest.param(
            "abuseipdb-get-blacklist",
            "get_blacklist_command",
            {"limit": "100", "days": "30", "confidence": "80", "saveToContext": "true"},
            {"nonexistent_param": "value"},
            id="get-blacklist-required-args-only",
        ),
    ],
)
class TestCommandsWithHttpRequest:
    """Tests for commands that do NOT take ``reliability`` and use the AbuseIPDB HTTP API."""

    def test_required_args_only(self, mocker, command_name, func_name, required_args, extra_args):
        """Test that the command works with only required arguments."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_http_request(mocker)

        func = getattr(AbuseDB, func_name)
        func(**required_args)

    def test_required_args_with_extra(self, mocker, command_name, func_name, required_args, extra_args):
        """Test that the command tolerates extra kwargs from ``**demisto.args()``."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_http_request(mocker)

        func = getattr(AbuseDB, func_name)
        args = {**required_args, **extra_args}
        func(**args)


class TestGetCategoriesCommandArgumentHandling:
    """Tests for ``get_categories_command`` which takes no arguments and needs no HTTP mock."""

    def test_required_args_only(self, mocker):
        """Test that get_categories_command works when called with no arguments."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)

        AbuseDB.get_categories_command()

    def test_required_args_with_extra(self, mocker):
        """Test that get_categories_command tolerates extra kwargs from ``**demisto.args()``."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)

        AbuseDB.get_categories_command(**{"nonexistent_param": "value"})


class TestGetFplistCommandArgumentHandling:
    """Tests for ``get_fplist_command`` which uses the Abuse.ch Hunting API."""

    def test_required_args_only(self, mocker):
        """Test that get_fplist_command works with only required arguments."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_abusech_http_request(mocker)

        AbuseDB.get_fplist_command(format="json", limit=1, all_results=False)

    def test_required_args_with_extra(self, mocker):
        """Test that get_fplist_command tolerates extra kwargs from ``**demisto.args()``."""
        import AbuseDB

        _mock_abuseipdb_globals(mocker)
        _mock_abusech_http_request(mocker)

        AbuseDB.get_fplist_command(format="json", limit=1, all_results=False, nonexistent_param="value")  # type: ignore[call-arg]