HelloWorld

This is the Hello World integration for getting started.

Utilities · HelloWorld

Details

IDHelloWorld
ProviderOpen Source
CategoryUtilities
From Version6.8.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM Exposure Management

README

This is the Hello World integration for getting started.

Configure HelloWorld on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for HelloWorld.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Source Reliability Reliability of the source providing the intelligence data. False
    Server URL (e.g., https://api.xsoar-example.com)   True
    Fetch alerts   False
    Alert type   False
    Maximum number of alerts per fetch   False
    API Key   True
    Score threshold for IP reputation command Set this to determine the HelloWorld score that will determine if an IP is malicious (0-100) False
    Severity of alerts to fetch   True
    First fetch time   False
    Trust any certificate (not secure)   False
    Use system proxy settings   False
  4. Click Test to validate the URLs, token, 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.

helloworld-say-hello


Hello command - prints hello to anyone.

Base Command

helloworld-say-hello

Input

Argument Name Description Required
name The name of whom you want to say hello to. Optional

Context Output

Path Type Description
hello String Should be Hello **something** here.

Command example

!helloworld-say-hello name="Hello Dbot"

Context Example

{
    "hello": "Hello Hello Dbot"
}

Human Readable Output

Hello Hello Dbot

helloworld-alert-list


Lists the example alerts as it would be fetched from the API.

Base Command

helloworld-alert-list

Input

Argument Name Description Required
alert_id Filter by alert item ID. If not provided, all IDs will be retrieved. Optional
limit How many alerts to fetch. Default is 10. Optional
severity The severity by which to filter the alerts. Optional

Context Output

Path Type Description
HelloWorld.alert.id Number The ID of the alert.
HelloWorld.alert.name String The name of the alert.
HelloWorld.alert.severity String The severity of the alert.
HelloWorld.alert.date Date The date of the alert occurrence.
HelloWorld.alert.status String The status of the alert.

Command example

!helloworld-alert-list limit="3" severity="low"

Context Example

{
    "HelloWorld": {
        "Alert": [
            {
                "date": "2023-09-14T11:30:39.882955",
                "id": 1,
                "name": "XSOAR Test Alert #1",
                "severity": "low",
                "status": "Testing"
            },
            {
                "date": "2023-09-14T11:30:39.882955",
                "id": 2,
                "name": "XSOAR Test Alert #2",
                "severity": "low",
                "status": "Testing"
            },
            {
                "date": "2023-09-14T11:30:39.882955",
                "id": 3,
                "name": "XSOAR Test Alert #3",
                "severity": "low",
                "status": "Testing"
            }
        ]
    }
}

Human Readable Output

Items List (Sample Data)

date id name severity status
2023-09-14T11:30:39.882955 1 XSOAR Test Alert #1 low Testing
2023-09-14T11:30:39.882955 2 XSOAR Test Alert #2 low Testing
2023-09-14T11:30:39.882955 3 XSOAR Test Alert #3 low Testing

Command example

!helloworld-alert-list alert_id=2

Context Example

{
    "HelloWorld": {
        "Alert": {
            "date": "2023-09-14T11:30:39.882955",
            "id": 2,
            "name": "XSOAR Test Alert #2",
            "severity": "low",
            "status": "Testing"
        }
    }
}

Human Readable Output

Items List (Sample Data)

date id name severity status
2023-09-14T11:30:39.882955 2 XSOAR Test Alert #2 low Testing

helloworld-alert-note-create


Example of creating a new item in the API.

Base Command

helloworld-alert-note-create

Input

Argument Name Description Required
alert_id The alert’s ID to add the note to. Required
note_text The comment to add to the note. Required

Context Output

Path Type Description
HelloWorld.alert.id Number The ID of the alert.
HelloWorld.alert.name String The name of the alert.
HelloWorld.alert.severity String The severity of the alert.
HelloWorld.alert.date Date The date of the alert occurrence.
HelloWorld.alert.status String The status of the alert.

Command example

!helloworld-alert-note-create alert_id=2 note_text=test

Context Example

{
    "HelloWorld": {
        "Note": {
            "msg": "Note was created for alert #2 successfully with comment='test'",
            "status": "success"
        }
    }
}

Human Readable Output

Note was created successfully.

ip


Return IP information and reputation.

Base Command

ip

Input

Argument Name Description Required
ip List of IPs. Optional
threshold If the IP has a reputation above the threshold, then the IP is defined as malicious. If threshold is not set, then the threshold from the instance configuration is used. Default is 65. Optional

Context Output

Path Type Description
DBotScore.Indicator String The indicator that was tested.
DBotScore.Score Number The actual score.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
HelloWorld.IP.asn String The autonomous system name for the IP address.
HelloWorld.IP.asn_cidr String The ASN CIDR.
HelloWorld.IP.asn_country_code String The ASN country code.
HelloWorld.IP.asn_date Date The date on which the ASN was assigned.
HelloWorld.IP.asn_description String The ASN description.
HelloWorld.IP.asn_registry String The registry the ASN belongs to.
HelloWorld.IP.entities String Entities associated to the IP.
HelloWorld.IP.ip String The actual IP address.
HelloWorld.IP.network.cidr String Network CIDR for the IP address.
HelloWorld.IP.network.country Unknown The country of the IP address.
HelloWorld.IP.network.end_address String The last IP address of the CIDR.
HelloWorld.IP.network.events.action String The action that happened on the event.
HelloWorld.IP.network.events.actor Unknown The actor that performed the action on the event.
HelloWorld.IP.network.events.timestamp String The timestamp when the event occurred.
HelloWorld.IP.network.handle String The handle of the network.
HelloWorld.IP.network.ip_version String The IP address version.
HelloWorld.IP.network.links String Links associated to the IP address.
HelloWorld.IP.network.name String The name of the network.
HelloWorld.IP.network.notices.description String The description of the notice.
HelloWorld.IP.network.notices.links Unknown Links associated with the notice.
HelloWorld.IP.network.notices.title String Title of the notice.
HelloWorld.IP.network.parent_handle String Handle of the parent network.
HelloWorld.IP.network.raw Unknown Additional raw data for the network.
HelloWorld.IP.network.remarks Unknown Additional remarks for the network.
HelloWorld.IP.network.start_address String The first IP address of the CIDR.
HelloWorld.IP.network.status String Status of the network.
HelloWorld.IP.network.type String The type of the network.
HelloWorld.IP.query String IP address that was queried.
HelloWorld.IP.raw Unknown Additional raw data for the IP address.
HelloWorld.IP.score Number Reputation score from HelloWorld for this IP (0 to 100, where higher is worse).
IP.Address String IP address.
IP.Malicious.Vendor String The vendor reporting the IP address as malicious.
IP.Malicious.Description String A description explaining why the IP address was reported as malicious.
IP.ASN String The autonomous system name for the IP address.
IP.Relationships.EntityA string The source of the relationship.
IP.Relationships.EntityB string The destination of the relationship.
IP.Relationships.Relationship string The name of the relationship.
IP.Relationships.EntityAType string The type of the source of the relationship.
IP.Relationships.EntityBType string The type of the destination of the relationship.

Command example

!ip ip="8.8.8.8"

Context Example

{
    "DBotScore": {
        "Indicator": "8.8.8.8",
        "Reliability": "C - Fairly reliable",
        "Score": 3,
        "Type": "ip",
        "Vendor": "HelloWorld Dev"
    },
    "HelloWorld": {
        "IP": {
            "id": "x.x.x.x",
            "ip": "8.8.8.8",
            "links": {
                "self": "https://www.virustotal.com/api/v3/ip_addresses/x.x.x.x"
            },
            "type": "ip_address"
        }
    },
    "IP": {
        "Address": "8.8.8.8",
        "Malicious": {
            "Description": "Hello World returned reputation -4",
            "Vendor": "HelloWorld Dev"
        },
        "Relationships": [
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "h",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "t",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "t",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "p",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "s",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ":",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "w",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "w",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "w",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ".",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "v",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "i",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "r",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "u",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "s",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "t",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "o",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "t",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "a",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "l",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ".",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "c",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "o",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "m",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "a",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "p",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "i",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "v",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "3",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "i",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "p",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "_",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "a",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "d",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "d",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "r",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "e",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "s",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "s",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "e",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "s",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "/",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "x",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ".",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "x",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ".",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "x",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": ".",
                "EntityBType": "URL",
                "Relationship": "related-to"
            },
            {
                "EntityA": "8.8.8.8",
                "EntityAType": "IP",
                "EntityB": "x",
                "EntityBType": "URL",
                "Relationship": "related-to"
            }
        ]
    }
}

Human Readable Output

IP (Sample Data)

id ip links type
x.x.x.x 8.8.8.8 self: https://www.virustotal.com/api/v3/ip_addresses/x.x.x.x ip_address

Attributes

as_owner asn continent country jarm last_analysis_stats last_modification_date network regional_internet_registry reputation tags total_votes whois_date
EMERALD-ONION   NA US :jarm: harmless: 72
malicious: 5
suspicious: 2
timeout: 0
undetected: 8
  :cidr: ARIN   values: harmless: 0
malicious: 1
 

Configuration parameters

  • integrationReliability — Source Reliability
  • url — Server URL (e.g., https://api.xsoar-example.com) (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • max_fetch — Maximum number of alerts per fetch
  • credentials — (required)
  • threshold_ip — Score threshold for IP reputation command
  • severity — Severity of alerts to fetch (required)
  • first_fetch — First fetch time
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (4)

  • helloworld-alert-list

    Lists the example alerts as it would be fetched from the API.

  • helloworld-alert-note-create

    Example of creating a new item in the API.

  • helloworld-say-hello

    Hello command - prints hello to anyone.

  • ip

    Return IP information and reputation.

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

This file contains the Unit Tests for the HelloWorld Integration based
on pytest. Cortex XSOAR contribution requirements mandate that every
integration should have a proper set of unit tests to automatically
verify that the integration is behaving as expected during CI/CD pipeline.

Test Execution
--------------

Unit tests can be checked in 3 ways:
- Using the command `lint` of demisto-sdk. The command will build a dedicated
  docker instance for your integration locally and use the docker instance to
  execute your tests in a dedicated docker instance.
- From the command line using `pytest -v` or `pytest -vv`
- From PyCharm

Example with demisto-sdk (from the content root directory):
demisto-sdk lint -i Packs/HelloWorld/Integrations/HelloWorld

Coverage
--------

There should be at least one unit test per command function. In each unit
test, the target command function is executed with specific parameters and the
output of the command function is checked against an expected output.

Unit tests should be self contained and should not interact with external
resources like (API, devices, ...). To isolate the code from external resources
you need to mock the API of the external resource using pytest-mock:
https://github.com/pytest-dev/pytest-mock/

In the following code we configure requests-mock (a mock of Python requests)
before each test to simulate the API calls to the HelloWorld API. This way we
can have full control of the API behavior and focus only on testing the logic
inside the integration code.

We recommend to use outputs from the API calls and use them to compare the
results when possible. See the ``test_data`` directory that contains the data
we use for comparison, in order to reduce the complexity of the unit tests and
avoding to manually mock all the fields.

NOTE: we do not have to import or build a requests-mock instance explicitly.
requests-mock library uses a pytest specific mechanism to provide a
requests_mock instance to any function with an argument named requests_mock.

More Details
------------

More information about Unit Tests in Cortex XSOAR:
https://xsoar.pan.dev/docs/integrations/unit-testing

"""

import json
from CommonServerPython import DemistoException
from HelloWorld import (
    Client,
    ip_reputation_command,
    alert_list_command,
    validate_api_key,
    dedup_by_ids,
    alert_note_create_command,
    fetch_incidents,
)
import pytest


EXAMPLE_RES_LIST = [
    {
        "id": 1,
        "alert_id": 1000,
        "kind": "Realtime",
        "date": "2021-05-20T12:40:55.662949Z",
    },
    {"id": 2, "alert_id": 2000, "kind": "Realtime", "date": "2021-05-20T12:40:56.662949Z"},
    {"id": 3, "alert_id": 3000, "kind": "Realtime", "date": "2021-05-20T12:40:57.662949Z"},
    {"id": 4, "alert_id": 4000, "kind": "Realtime", "date": "2021-05-20T12:40:58.662949Z"},
]


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


def create_mock_client():
    return Client(base_url="https://api.example.com", verify=False, headers={"Authentication": "Token some_api_key"})


def test_say_hello():
    """
    Tests helloworld-say-hello command function.

        Given:
            - No mock is needed here because the say_hello_command does not call any external API.

        When:
            - Running the 'say_hello_command'.

        Then:
            - Checks the output of the command function with the expected output.

    """
    from HelloWorld import Client, say_hello_command

    client = Client(base_url="https://test.com/api/v1", verify=False, auth=("test", "test"))
    args = {"name": "Dbot"}
    response = say_hello_command(client, args)

    assert response.outputs == "Hello Dbot"


@pytest.mark.parametrize(
    "hello_world_severity, expected_xsoar_severity", [("low", 1), ("medium", 2), ("high", 3), ("critical", 4), ("unknown", 0)]
)
def test_convert_to_demisto_severity(hello_world_severity, expected_xsoar_severity):
    """
    Given:
        - A string represents a HelloWorld severity.

    When:
        - Running the 'convert_to_demisto_severity' function.

    Then:
        - Verify that the severity was correctly translated to a Cortex XSOAR severity.
    """
    from HelloWorld import convert_to_demisto_severity

    assert convert_to_demisto_severity(hello_world_severity) == expected_xsoar_severity


def test_api_key():
    """Validates an API key. Since this is a tutorial, there is a dummy API key.

    When:
        - An API key is provided as a string argument
    Then:
        - The key is checked against known valid keys
        - If invalid, an exception is raised with a clear message.
    """
    api_key = "some_api_key"

    with pytest.raises(DemistoException):
        validate_api_key(api_key)


@pytest.mark.parametrize(
    "alerts, ids_to_compare, expected",
    [
        ([{"id": 1}, {"id": 2}], [2, 3], ([{"id": 1}], 1)),
        ([{"id": 2}, {"id": 3}], [2, 3], ([], 2)),
        ([{"id": 4}, {"id": 5}], [2, 3], ([{"id": 4}, {"id": 5}], 0)),
    ],
)
def test_dedup_by_ids(alerts, ids_to_compare, expected):
    """
    Given:
        - A list of alerts
        - A list of IDs to compare against

    When:
        - Running dedup_by_ids() with the alerts and ID list

    Then:
        - Ensure the deduped alerts match the expected
        - Ensure the number of duplicates match the expected
    """
    deduped, dups = dedup_by_ids(alerts, ids_to_compare)
    assert deduped == expected[0]
    assert dups == expected[1]


class TestIPCommand:
    @pytest.fixture(autouse=True)
    def setup(self):
        self.mocked_client = create_mock_client()

    def test_ip_reputation_command_single_ip(self, mocker):
        """
        Given:
            - Args with a single IP

        When:
            - Mock client returns reputation for the IP
            - Run ip_reputation_command with args

        Then:
            - Validate expected number of results
            - Validate IP address in outputs
            - Validate DBotScore outputs as expected
        """
        args = {"ip": "8.8.8.8"}
        mocker.patch.object(self.mocked_client, "get_ip_reputation", return_value={"attributes": {"reputation": 80}})

        result = ip_reputation_command(self.mocked_client, args, default_threshold=60, reliability="A - Completely reliable")

        assert len(result) == 1
        assert len(result[0].outputs) == 1  # type: ignore
        ip_output = result[0].to_context()["EntryContext"]["IP(val.Address && val.Address == obj.Address)"]
        assert ip_output
        assert ip_output[0]["Address"] == "8.8.8.8"

        dbot_output = result[0].to_context()["EntryContext"][
            "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
        ]
        assert dbot_output[0]["Indicator"] == "8.8.8.8"
        assert dbot_output[0]["Score"] == 1

    def test_ip_reputation_command_multiple_ips(self, mocker):
        """
        Given:
            - A list of IP addresses as input

        When:
            - Mock client returns reputation scores for each IP
            - Run ip_reputation_command with multiple IPs

        Then:
            - Ensure correct number of results returned
            - Validate indicator and score for each result
        """
        args = {"ip": ["8.8.8.8", "1.1.1.1"]}
        mocker.patch.object(
            self.mocked_client,
            "get_ip_reputation",
            side_effect=[{"attributes": {"reputation": 80}}, {"attributes": {"reputation": 20}}],
        )

        result = ip_reputation_command(self.mocked_client, args, default_threshold=60, reliability="A - Completely reliable")

        assert len(result) == 2
        dbot_output_1 = result[0].to_context()["EntryContext"][
            "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
        ]
        assert dbot_output_1[0]["Score"] == 1
        assert dbot_output_1[0]["Indicator"] == "8.8.8.8"

        dbot_output_2 = result[1].to_context()["EntryContext"][
            "DBotScore(val.Indicator && val.Indicator == obj.Indicator && val.Vendor == obj.Vendor && val.Type == obj.Type)"
        ]
        assert dbot_output_2[0]["Indicator"] == "1.1.1.1"
        assert dbot_output_2[0]["Score"] == 3

    def test_ip_reputation_command_invalid_ip(self):
        """
        Given:
            - Args with an invalid IP address

        When:
            - Running ip_reputation_command with the invalid IP

        Then:
            - Should raise ValueError
        """
        args = {"ip": "invalid"}

        with pytest.raises(ValueError):
            ip_reputation_command(self.mocked_client, args, default_threshold=60, reliability="A - Completely reliable")


class TestAlertListCommand:
    @pytest.fixture(autouse=True)
    def setup(self):
        self.mocked_client = create_mock_client()

    # DEVELOPER TIP
    # Using the parametrize fixture helps you generate many test cases on the same function,
    # to make sure you are fully covered.
    # Make sure to check all edge-cases such as empty responses, wrong inputs, etc.
    # When using parametrize it's optional to add an ID, which will make test failure easier to debug.

    @pytest.mark.parametrize(
        "args, expected_limit, expected_severity",
        (
            pytest.param({"alert_id": 1}, 1, "low", id="given id"),  # expecting one record
            pytest.param(
                {"alert_id": 2, "severity": "high"}, 1, "high", id="given id and wrong severity"
            ),  # expecting one record ignoring severity
            pytest.param(
                {"alert_id": 3, "limit": 5}, 1, "low", id="given id, no severity and limit"
            ),  # expecting one record ignoring limit
        ),
    )
    def test_alert_list_given_id(self, mocker, args, expected_limit, expected_severity):
        """
        Given:
            - Args containing alert_id
        When:
            - Calling alert_list_command with mocked client
        Then:
            - The correct function is being called (single alert) correctly
        """
        single_id_call_mock = mocker.patch.object(self.mocked_client, "get_alert", return_value={"id": 1, "title": "alert 1"})

        result = alert_list_command(self.mocked_client, args)

        assert result.readable_output.startswith("### Items List (Sample Data)")
        assert len(result.outputs) == expected_limit
        single_id_call_mock.assert_called()
        single_id_call_mock.assert_called_once_with(args["alert_id"])

    @pytest.mark.parametrize(
        "args, expected_limit, expected_severity",
        (pytest.param({"severity": "high", "limit": 3}, 3, "high", id="given limit and severity"),),
    )
    def test_alert_list(self, mocker, args, expected_limit, expected_severity):
        """
        Given:
            - Args

        When:
            - Calling alert_list_command with mocked client and args

        Then:
            - Ensure mocked client method called correctly
            - Validate returned command results contain data
        """
        mocked_list_call = mocker.patch.object(
            self.mocked_client,
            "get_alert_list",
            return_value=[{"id": 1, "title": "alert 1"}, {"id": 2, "title": "alert 2"}, {"id": 3, "title": "alert 3"}],
        )

        result = alert_list_command(self.mocked_client, args)

        mocked_list_call.assert_called()
        mocked_list_call.assert_called_once_with(limit=expected_limit, severity=expected_severity)
        assert result.readable_output.startswith("### Items List (Sample Data)")  # type: ignore

    @pytest.mark.parametrize(
        "args",
        (
            pytest.param({"limit": 3}, id="given only limit"),
            pytest.param({}, id="given empty args"),
        ),
    )
    def test_alert_list_fail(self, mocker, args):
        """
        Given:
            - Args without severity
        When:
            - Calling alert_list_command with mocked client and args
        Then:
            - Should raise DemistoException as severity is required
        """
        mocker.patch.object(
            self.mocked_client,
            "get_alert_list",
            return_value=[{"id": 1, "title": "alert 1"}, {"id": 2, "title": "alert 2"}, {"id": 3, "title": "alert 3"}],
        )

        with pytest.raises(DemistoException):
            alert_list_command(self.mocked_client, args)


class TestAlertNoteCreate:
    @pytest.fixture(autouse=True)
    def init(self):
        self.client = create_mock_client()

    def test_success(self, mocker):
        args = {"alert_id": 123, "note_text": "Test note"}
        mocker.patch.object(self.client, "create_note", return_value={"status": "success"})

        result = alert_note_create_command(self.client, args)

        assert result.readable_output == "Note was created successfully."
        assert result.outputs["status"] == "success"


class TestFetchAlerts:
    EXAMPLE_ALERTS = [{"id": 1, "name": "Incident 1"}, {"id": 2, "name": "Incident 2"}, {"id": 3}]

    @pytest.fixture(autouse=True)
    def setup(self):
        self.client = create_mock_client()

    def test_first_run(self, mocker):
        """
        Given:
            - An empty last run
            - A first fetch time
        When:
            - Running fetch_incidents for the first time
            - Mocking the client's alert list response
        Then:
            - Returned incidents should match mocked alerts
            - Next run should have the last alert IDs
        """
        last_run: dict = {}
        first_fetch = "2021-01-01T00:00:00Z"
        mocker.patch.object(self.client, "get_alert_list", return_value=self.EXAMPLE_ALERTS)

        next_run, incidents = fetch_incidents(self.client, max_results=3, last_run=last_run, first_fetch_time=first_fetch)

        # Assertions
        assert incidents[0]["name"] == "Incident 1"
        assert incidents[1]["name"] == "Incident 2"
        assert incidents[2]["name"] == "Hello World Alert"

        assert next_run["last_ids"] == [1, 2, 3]

    def test_subsequent_run(self):
        """
        Given:
            - A last run with a last fetch time and list of last incident IDs
        When:
            - Fetch incidents is called with the last run
            - First fetch time is provided
        Then:
            - Returned incidents should have occurred after last fetch
            - Number of returned incidents should match max results
            - Next run should have new updated last incident IDs
        """
        last_run = {"last_fetch": "2021-02-01T00:00:00Z", "last_ids": [1, 2, 3]}
        first_fetch = "2021-01-01T00:00:00Z"

        next_run, incidents = fetch_incidents(self.client, max_results=3, last_run=last_run, first_fetch_time=first_fetch)

        assert incidents[0]["occurred"] > last_run["last_fetch"]
        assert len(incidents) == 3
        assert next_run["last_ids"] == [4, 5, 6]