SearchIndicatorAgentix

This script searches indicators by specified fields.

python · Common Scripts

Details

IDSearchIndicatorAgentix
Languagepython
From Version6.5.0
Docker Imagedemisto/python3:3.12.13.10116658
TagsUtility
from SearchIndicatorAgentix import (
    escape_special_characters,
    build_query_for_indicator_values,
    prepare_query,
    KEYS_TO_EXCLUDE_FROM_QUERY,
    build_query_excluding_indicator_values,
    search_indicators,
)
import json
import pytest


def test_escape_special_characters_backslash():
    """
    Given: A value containing backslash characters
    When: escape_special_characters is called
    Then: Returns value with backslashes properly escaped
    """
    value = "test\\path\\file"
    expected = "test\\\\path\\\\file"
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_quotes():
    """
    Given: A value containing double quotes
    When: escape_special_characters is called
    Then: Returns value with quotes properly escaped
    """
    value = 'This is a "quoted" string'
    expected = 'This\\ is\\ a\\ \\"quoted\\"\\ string'
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_newlines_and_tabs():
    """
    Given: A value containing newlines, tabs, and carriage returns
    When: escape_special_characters is called
    Then: Returns value with whitespace characters properly escaped
    """
    value = "line1\nline2\tcolumn\rreturn"
    expected = "line1\\nline2\\tcolumn\\rreturn"
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_colon_and_caret():
    """
    Given: A value containing colons and caret characters
    When: escape_special_characters is called
    Then: Returns value with special query characters properly escaped
    """
    value = "field:value^boost"
    expected = "field\\:value\\^boost"
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_spaces():
    """
    Given: A value containing spaces
    When: escape_special_characters is called
    Then: Returns value with spaces properly escaped
    """
    value = "hello world test"
    expected = "hello\\ world\\ test"
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_mixed_special_chars():
    """
    Given: A value containing multiple different special characters
    When: escape_special_characters is called
    Then: Returns value with all special characters properly escaped
    """
    value = 'path\\file:"value" test\nline^boost'
    expected = 'path\\\\file\\:\\"value\\"\\ test\\nline\\^boost'
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_empty_string():
    """
    Given: An empty string
    When: escape_special_characters is called
    Then: Returns empty string unchanged
    """
    value = ""
    expected = ""
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_no_special_chars():
    """
    Given: A value containing no special characters
    When: escape_special_characters is called
    Then: Returns the original value unchanged
    """
    value = "normaltext123"
    expected = "normaltext123"
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_only_special_chars():
    """
    Given: A value containing only special characters
    When: escape_special_characters is called
    Then: Returns all characters properly escaped
    """
    value = '\\ \n\t\r"^:'
    expected = '\\\\\\ \\n\\t\\r\\"\\^\\:'
    result = escape_special_characters(value)
    assert result == expected


def test_escape_special_characters_repeated_chars():
    """
    Given: A value with repeated special characters
    When: escape_special_characters is called
    Then: Returns value with each occurrence properly escaped
    """
    value = "test::value^^boost  space"
    expected = "test\\:\\:value\\^\\^boost\\ \\ space"
    result = escape_special_characters(value)
    assert result == expected


@pytest.mark.parametrize(
    "n,expected_chunks",
    [
        (0, 0),  # empty -> []
        (50, 1),  # <100 -> one chunk
        (100, 1),  # exactly 100 -> one chunk
        (110, 2),  # 100 + 10 -> two chunks
        (250, 3),  # 100 + 100 + 50 -> three chunks
    ],
)
def test_build_query_for_indicator_values_chunk_counts(n, expected_chunks):
    # build n indicator values
    values = [f"v{i}" for i in range(n)]
    args = {"value": json.dumps(values)}

    result = build_query_for_indicator_values(args)

    # Check number of chunk queries returned
    assert isinstance(result, list)
    assert len(result) == expected_chunks

    # Optionally, verify each chunk's size by counting " OR " occurrences (+1 = items in chunk)
    # because our stub joins values with " OR " and build_query wraps with parentheses.
    remaining = n
    for chunk_str in result:
        # strip outer parentheses added in build_query_for_indicator_values
        inner = chunk_str[1:-1]
        items_in_chunk = 0 if inner == "" else inner.count(" OR ") + 1
        expected_size = min(100, remaining)
        assert items_in_chunk == expected_size
        remaining -= expected_size

    assert remaining == 0


def test_build_query_for_values_empty_args():
    """
    Given: Empty arguments dictionary
    When: build_query_for_values is called
    Then: Returns empty list
    """
    args = {}
    result = build_query_for_indicator_values(args)
    assert result == []


def test_build_query_for_values_no_value_key():
    """
    Given: Arguments without 'value' key
    When: build_query_for_values is called
    Then: Returns empty list
    """
    args = {"type": "Domain", "verdict": "Malicious"}
    result = build_query_for_indicator_values(args)
    assert result == []


def test_build_query_for_values_empty_value_list():
    """
    Given: Arguments with empty value list
    When: build_query_for_values is called
    Then: Returns empty list
    """
    args = {"value": []}
    result = build_query_for_indicator_values(args)
    assert result == []


def test_build_query_for_values_single_value():
    """
    Given: Arguments with single value in JSON string format
    When: build_query_for_values is called
    Then: Returns list with one properly formatted query
    """

    args = {"value": json.dumps(["example"])}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]


def test_build_query_for_values_multiple_values_under_100():
    """
    Given: Arguments with multiple values under 100 limit
    When: build_query_for_values is called
    Then: Returns list with one query containing OR operators
    """

    values = ["example", "test", "sample"]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    for value in values:
        assert f'value:"{value}"' in result[0]
    assert " OR " in result[0]


def test_build_query_for_values_exactly_100_values():
    """
    Given: Arguments with exactly 100 values
    When: build_query_for_values is called
    Then: Returns list with one query containing all values
    """

    values = [f"example{i}" for i in range(100)]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    assert "example0" in result[0]
    assert "example99" in result[0]


def test_build_query_for_values_over_100_values():
    """
    Given: Arguments with over 100 values
    When: build_query_for_values is called
    Then: Returns multiple queries with chunked values
    """

    values = [f"example{i}" for i in range(150)]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 2
    assert "example0" in result[0]
    assert "example99" in result[0]
    assert "example100" in result[1]
    assert "example149" in result[1]


def test_build_query_for_values_exactly_101_values():
    """
    Given: Arguments with exactly 101 values
    When: build_query_for_values is called
    Then: Returns two queries with 100 and 1 values respectively
    """

    values = [f"test{i}" for i in range(101)]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 2
    assert "test0" in result[0]
    assert "test99" in result[0]
    assert "test100" in result[1]
    assert " OR " not in result[1]


def test_build_query_for_values_with_special_characters():
    """
    Given: Arguments with values containing special characters
    When: build_query_for_values is called
    Then: Returns queries with properly escaped special characters
    """

    values = ["test with spaces", 'test"quotes', "test\\backslash"]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    assert "test\\ with\\ spaces" in result[0]
    assert 'test\\"quotes' in result[0]
    assert "test\\\\backslash" in result[0]


def test_build_query_for_values_with_whitespace():
    """
    Given: Arguments with values containing leading/trailing whitespace
    When: build_query_for_values is called
    Then: Returns queries with whitespace stripped from values
    """

    values = ["  example  ", "\ttest\n", " sample "]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert 'value:"test"' in result[0]
    assert 'value:"sample"' in result[0]


def test_build_query_for_values_mixed_data_types():
    """
    Given: Arguments with values of mixed data types (strings, numbers)
    When: build_query_for_values is called
    Then: Returns queries with all values converted to strings
    """

    values = ["example", 192168001001, True, None]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert 'value:"192168001001"' in result[0]
    assert 'value:"True"' in result[0]
    assert 'value:"None"' in result[0]


def test_build_query_for_values_large_chunk_boundary():
    """
    Given: Arguments with 250 values (tests multiple chunks)
    When: build_query_for_values is called
    Then: Returns three queries with proper chunk distribution
    """
    values = [f"domain{i}.example" for i in range(250)]
    args = {"value": json.dumps(values)}
    result = build_query_for_indicator_values(args)
    assert len(result) == 3
    assert "domain0.example" in result[0]
    assert "domain99.example" in result[0]
    assert "domain100.example" in result[1]
    assert "domain199.example" in result[1]
    assert "domain200.example" in result[2]
    assert "domain249.example" in result[2]


def test_prepare_query_empty_args():
    """
    Given: Empty arguments dictionary
    When: prepare_query is called
    Then: Returns empty list
    """
    args = {}
    result = prepare_query(args)
    assert result == []


def test_prepare_query_only_value_filters():
    """
    Given: Arguments with only value filters
    When: prepare_query is called
    Then: Returns queries containing only value filters
    """
    args = {"value": json.dumps(["example", "test"])}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert 'value:"test"' in result[0]
    assert " OR " in result[0]
    assert " AND " not in result[0]


def test_prepare_query_only_field_filters():
    """
    Given: Arguments with only field filters (no values)
    When: prepare_query is called
    Then: Returns empty list as no value filters exist
    """
    args = {"type": "Domain", "verdict": "Malicious"}
    result = prepare_query(args)
    assert result == ['(type:"Domain") AND (verdict:"Malicious")']


def test_prepare_query_value_and_field_filters():
    """
    Given: Arguments with both value and field filters
    When: prepare_query is called
    Then: Returns queries combining value and field filters with AND
    """

    args = {"value": json.dumps(["example"]), "type": json.dumps(["Domain"]), "verdict": json.dumps(["Malicious"])}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert "type:" in result[0]
    assert "verdict:" in result[0]
    assert "Domain" in result[0]
    assert "Malicious" in result[0]
    assert " AND " in result[0]


def test_prepare_query_multiple_value_chunks():
    """
    Given: Arguments with over 100 values requiring chunking
    When: prepare_query is called
    Then: Returns multiple queries each combined with field filters
    """

    values = [f"example{i}" for i in range(150)]
    args = {"value": json.dumps(values), "type": json.dumps(["Domain"])}
    result = prepare_query(args)
    assert len(result) == 2
    for query in result:
        assert "type:" in query
        assert "Domain" in query
        assert " AND " in query


def test_prepare_query_single_value_with_fields():
    """
    Given: Arguments with single value and multiple field filters
    When: prepare_query is called
    Then: Returns single query with value and all field filters
    """

    args = {
        "value": json.dumps(["test.example"]),
        "type": json.dumps(["Domain"]),
        "verdict": json.dumps(["Malicious"]),
        "score": json.dumps(["High"]),
    }
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"test.example"' in result[0]
    assert "type:" in result[0]
    assert "verdict:" in result[0]
    assert "score:" in result[0]
    assert result[0].count(" AND ") == 3


def test_prepare_query_empty_value_list():
    """
    Given: Arguments with empty value list and field filters
    When: prepare_query is called
    Then: Returns empty list
    """

    args = {"value": "[]", "type": "Domain"}
    result = prepare_query(args)
    assert result == ['(type:"Domain")']


def test_prepare_query_values_with_special_characters():
    """
    Given: Arguments with values containing special characters and field filters
    When: prepare_query is called
    Then: Returns queries with properly escaped values combined with fields
    """

    args = {"value": json.dumps(["test with spaces", 'test"quotes']), "type": "Domain"}
    result = prepare_query(args)
    assert len(result) == 1
    assert "test\\ with\\ spaces" in result[0]
    assert 'test\\"quotes' in result[0]
    assert "type:" in result[0]
    assert " AND " in result[0]


def test_prepare_query_excluded_keys_ignored():
    """
    Given: Arguments with value filters, valid fields, and excluded keys
    When: prepare_query is called
    Then: Returns queries excluding the excluded keys but including valid fields
    """

    excluded_key = KEYS_TO_EXCLUDE_FROM_QUERY[0] if KEYS_TO_EXCLUDE_FROM_QUERY else "dummy"
    args = {"value": json.dumps(["example"]), "type": "Domain", excluded_key: json.dumps(["excluded_value"])}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert "type:" in result[0]
    assert f"{excluded_key}:" not in result[0]


def test_prepare_query_issues_ids_transformation():
    """
    Given: Arguments with IssuesIDs field and value filters
    When: prepare_query is called
    Then: Returns queries with IssuesIDs transformed to investigationIDs
    """

    args = {"value": json.dumps(["example"]), "IssuesIDs": "123,456"}
    result = prepare_query(args)
    assert len(result) == 1
    assert "investigationIDs:" in result[0]
    assert "IssuesIDs:" not in result[0]
    assert "123" in result[0]
    assert "456" in result[0]


def test_prepare_query_empty_fields_ignored():
    """
    Given: Arguments with value filters and empty field values
    When: prepare_query is called
    Then: Returns queries containing only value filters as empty fields are ignored
    """

    args = {"value": json.dumps(["example"]), "type": "", "verdict": ""}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert "type:" not in result[0]
    assert "verdict:" not in result[0]
    assert " AND " not in result[0]


def test_prepare_query_large_value_set_with_complex_fields():
    """
    Given: Arguments with 250 values and multiple complex field filters
    When: prepare_query is called
    Then: Returns three queries each combined with all field filters
    """

    values = [f"domain{i}.example" for i in range(250)]
    args = {
        "value": json.dumps(values),
        "type": json.dumps(["Domain", "IP"]),
        "verdict": json.dumps(["Malicious"]),
        "score": json.dumps(["High", "Medium"]),
    }
    result = prepare_query(args)
    assert len(result) == 3
    for query in result:
        assert "type:" in query
        assert "verdict:" in query
        assert "score:" in query
        assert query.count(" AND ") == 3


def test_prepare_query_mixed_data_types_in_values():
    """
    Given: Arguments with mixed data types in values and field filters
    When: prepare_query is called
    Then: Returns queries with all values converted to strings and combined with fields
    """

    args = {"value": json.dumps(["example", 192168001001, True]), "type": "Domain"}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert 'value:"192168001001"' in result[0]
    assert 'value:"True"' in result[0]
    assert "type:" in result[0]
    assert " AND " in result[0]


def test_prepare_query_whitespace_stripped_from_values():
    """
    Given: Arguments with values containing whitespace and field filters
    When: prepare_query is called
    Then: Returns queries with whitespace stripped from values
    """

    args = {"value": json.dumps(["  example  ", "\ttest.org\n"]), "type": "Domain"}
    result = prepare_query(args)
    assert len(result) == 1
    assert 'value:"example"' in result[0]
    assert 'value:"test.org"' in result[0]
    assert "type:" in result[0]
    assert " AND " in result[0]


def test_build_query_excluding_indicator_values_empty_args():
    """
    Given: Empty arguments dictionary
    When: build_query_excluding_indicator_values is called
    Then: Returns empty string
    """

    args = {}
    result = build_query_excluding_indicator_values(args)
    assert result == ""


def test_build_query_excluding_indicator_values_only_value_key():
    """
    Given: Arguments containing only 'value' key
    When: build_query_excluding_indicator_values is called
    Then: Returns empty string as value key is excluded
    """

    args = {"value": json.dumps(["example", "test"])}
    result = build_query_excluding_indicator_values(args)
    assert result == ""


def test_build_query_excluding_indicator_values_single_field():
    """
    Given: Arguments with single field excluding value
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with single field condition in parentheses
    """

    args = {"type": "Domain"}
    result = build_query_excluding_indicator_values(args)
    assert result == '(type:"Domain")'


def test_build_query_excluding_indicator_values_multiple_fields():
    """
    Given: Arguments with multiple fields excluding value
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with multiple field conditions joined by AND
    """

    args = {"type": json.dumps(["Domain"]), "verdict": "Malicious"}
    result = build_query_excluding_indicator_values(args)
    assert "(type:" in result
    assert "(verdict:" in result
    assert "Domain" in result
    assert "Malicious" in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_with_value_field_mixed():
    """
    Given: Arguments with value field and other fields
    When: build_query_excluding_indicator_values is called
    Then: Returns query string excluding value field but including other fields
    """

    args = {"value": json.dumps(["example"]), "type": "Domain", "verdict": "Malicious"}
    result = build_query_excluding_indicator_values(args)
    assert "value:" not in result
    assert "type:" in result
    assert "verdict:" in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_issues_ids_transformation():
    """
    Given: Arguments with IssuesIDs field
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with IssuesIDs transformed to investigationIDs
    """
    args = {"IssuesIDs": "123,456"}
    result = build_query_excluding_indicator_values(args)
    assert "investigationIDs:" in result
    assert "IssuesIDs:" not in result
    assert "123" in result
    assert "456" in result


def test_build_query_excluding_indicator_values_excluded_keys_ignored():
    """
    Given: Arguments with fields in KEYS_TO_EXCLUDE_FROM_QUERY
    When: build_query_excluding_indicator_values is called
    Then: Returns query string excluding the excluded keys
    """

    excluded_key = KEYS_TO_EXCLUDE_FROM_QUERY[0] if KEYS_TO_EXCLUDE_FROM_QUERY else "dummy"
    args = {"type": "Domain", excluded_key: "excluded_value"}
    result = build_query_excluding_indicator_values(args)
    assert "type:" in result
    assert f"{excluded_key}:" not in result


def test_build_query_excluding_indicator_values_empty_field_values():
    """
    Given: Arguments with empty field values
    When: build_query_excluding_indicator_values is called
    Then: Returns empty string as empty fields are ignored
    """

    args = {"type": [], "verdict": "", "score": None}
    result = build_query_excluding_indicator_values(args)
    assert result == ""


def test_build_query_excluding_indicator_values_mixed_empty_and_valid_fields():
    """
    Given: Arguments with mix of empty and valid field values
    When: build_query_excluding_indicator_values is called
    Then: Returns query string containing only valid fields
    """

    args = {"type": "Domain", "verdict": "", "score": "High"}
    result = build_query_excluding_indicator_values(args)
    assert "type:" in result
    assert "score:" in result
    assert "verdict:" not in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_multiple_values_in_field():
    """
    Given: Arguments with field containing multiple values
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with OR operators between multiple values
    """

    args = {"type": "Domain,IP,URL"}
    result = build_query_excluding_indicator_values(args)
    assert "(type:" in result
    assert "Domain" in result
    assert "IP" in result
    assert "URL" in result
    assert " OR " in result


def test_build_query_excluding_indicator_values_single_value_no_or():
    """
    Given: Arguments with field containing single value
    When: build_query_excluding_indicator_values is called
    Then: Returns query string without OR operators
    """

    args = {"type": "Domain"}
    result = build_query_excluding_indicator_values(args)
    assert "(type:" in result
    assert "Domain" in result
    assert " OR " not in result


def test_build_query_excluding_indicator_values_complex_multiple_fields():
    """
    Given: Arguments with multiple fields each having multiple values
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with proper AND/OR structure
    """

    args = {
        "type": "Domain,IP",
        "verdict": "Malicious,Suspicious",
        "score": "High",
    }
    result = build_query_excluding_indicator_values(args)
    assert result.count("(") == 3
    assert result.count(")") == 3
    assert result.count(" AND ") == 2
    assert "type:" in result
    assert "verdict:" in result
    assert "score:" in result


def test_build_query_excluding_indicator_values_non_json_string_values():
    """
    Given: Arguments with field values as plain strings (not JSON)
    When: build_query_excluding_indicator_values is called
    Then: Returns query string treating plain strings as single values
    """

    args = {"type": "Domain", "verdict": "Malicious"}
    result = build_query_excluding_indicator_values(args)
    assert "type:" in result
    assert "verdict:" in result
    assert "Domain" in result
    assert "Malicious" in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_mixed_json_and_plain_values():
    """
    Given: Arguments with mix of JSON and plain string field values
    When: build_query_excluding_indicator_values is called
    Then: Returns query string handling both value types correctly
    """

    args = {"type": "Domain,IP", "verdict": "Malicious", "score": "High"}
    result = build_query_excluding_indicator_values(args)
    assert "type:" in result
    assert "verdict:" in result
    assert "score:" in result
    assert " OR " in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_issues_ids_with_other_fields():
    """
    Given: Arguments with IssuesIDs and other fields
    When: build_query_excluding_indicator_values is called
    Then: Returns query string with IssuesIDs transformed and combined with other fields
    """

    args = {"IssuesIDs": "123,456", "type": "Domain"}
    result = build_query_excluding_indicator_values(args)
    assert "investigationIDs:" in result
    assert "type:" in result
    assert "IssuesIDs:" not in result
    assert " AND " in result


def test_build_query_excluding_indicator_values_value_field_at_different_positions():
    """
    Given: Arguments with value field at beginning, middle, and end positions
    When: build_query_excluding_indicator_values is called
    Then: Returns query string excluding value field regardless of position
    """

    args = {"value": json.dumps(["test"]), "type": "Domain", "verdict": "Malicious"}
    result = build_query_excluding_indicator_values(args)
    assert "value:" not in result
    assert "type:" in result
    assert "verdict:" in result


def test_search_indicators_empty_args():
    """
    Given: Empty arguments dictionary
    When: search_indicators is called
    Then: Returns empty markdown and empty list as no queries are generated
    """
    args = {}
    markdown, filtered_indicators = search_indicators(args)
    assert filtered_indicators == []
    assert "Indicators Found" in markdown


def test_search_indicators_no_results(mocker):
    """
    Given: Valid arguments that generate queries but find no indicators
    When: search_indicators is called
    Then: Returns empty results with proper markdown formatting
    """
    args = {"value": json.dumps(["nonexistent"])}

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": []}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert filtered_indicators == []
    assert "Indicators Found" in markdown


def test_search_indicators_single_query_single_result(mocker):
    """
    Given: Arguments that generate single query and return single indicator
    When: search_indicators is called
    Then: Returns properly formatted indicator with all required fields
    """
    args = {"value": json.dumps(["example.com"])}
    mock_indicator = {
        "id": "123",
        "indicator_type": "Domain",
        "value": "example",
        "score": 3,
        "expirationStatus": "Active",
        "investigationIDs": ["inv1"],
        "lastSeen": "2023-01-01",
    }

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": [mock_indicator]}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert len(filtered_indicators) == 1
    assert filtered_indicators[0]["id"] == "123"
    assert filtered_indicators[0]["verdict"] == "Malicious"
    assert "example" in markdown


def test_search_indicators_multiple_queries_multiple_results(mocker):
    """
    Given: Arguments generating multiple queries each returning indicators
    When: search_indicators is called
    Then: Returns combined results from all queries
    """
    values = [f"example{i}.com" for i in range(150)]
    args = {"value": json.dumps(values)}

    mock_indicator1 = {
        "id": "123",
        "indicator_type": "Domain",
        "value": "example1.com",
        "score": 2,
        "expirationStatus": "Active",
        "investigationIDs": [],
        "lastSeen": "2023-01-01",
    }

    mock_indicator2 = {
        "id": "456",
        "indicator_type": "Domain",
        "value": "example2.com",
        "score": 1,
        "expirationStatus": "Expired",
        "investigationIDs": ["inv2"],
        "lastSeen": "2023-01-02",
    }

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.side_effect = [[{"Contents": [mock_indicator1]}], [{"Contents": [mock_indicator2]}]]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert len(filtered_indicators) == 2
    assert filtered_indicators[0]["id"] == "123"
    assert filtered_indicators[1]["id"] == "456"
    assert mock_demisto.executeCommand.call_count == 2


def test_search_indicators_missing_fields_with_custom_fields(mocker):
    """
    Given: Indicator with missing standard fields but present in CustomFields
    When: search_indicators is called
    Then: Returns indicator with fields populated from CustomFields
    """
    args = {"value": json.dumps(["example"])}
    mock_indicator = {
        "id": "123",
        "value": "example",
        "CustomFields": {
            "indicator_type": "Domain",
            "score": 2,
            "expirationStatus": "Active",
            "investigationIDs": ["inv1"],
            "lastSeen": "2023-01-01",
        },
    }

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": [mock_indicator]}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert len(filtered_indicators) == 1
    assert filtered_indicators[0]["indicator_type"] == "Domain"
    assert filtered_indicators[0]["score"] == 2


def test_search_indicators_with_size_parameter(mocker):
    """
    Given: Arguments with size parameter to limit results
    When: search_indicators is called
    Then: Passes size parameter to executeCommand
    """
    args = {"value": json.dumps(["example.com"]), "size": 50}

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": []}]
    mock_demisto.debug.return_value = None

    search_indicators(args)

    mock_demisto.executeCommand.assert_called()
    call_args = mock_demisto.executeCommand.call_args[0][1]
    assert call_args["size"] == 50


def test_search_indicators_no_size_parameter(mocker):
    """
    Given: Arguments without size parameter
    When: search_indicators is called
    Then: Passes None as size to executeCommand
    """
    args = {"value": json.dumps(["example"])}

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": []}]
    mock_demisto.debug.return_value = None

    search_indicators(args)

    mock_demisto.executeCommand.assert_called()
    call_args = mock_demisto.executeCommand.call_args[0][1]
    assert call_args["size"] is None


def test_search_indicators_execute_command_returns_none(mocker):
    """
    Given: executeCommand returns None
    When: search_indicators is called
    Then: Handles None result gracefully and returns empty results
    """
    args = {"value": json.dumps(["example"])}

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = None
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert filtered_indicators == []


def test_search_indicators_execute_command_empty_result(mocker):
    """
    Given: executeCommand returns empty result structure
    When: search_indicators is called
    Then: Handles empty result gracefully
    """
    args = {"value": json.dumps(["example.com"])}

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert filtered_indicators == []


def test_search_indicators_score_to_reputation_conversion(mocker):
    """
    Given: Indicators with different score values
    When: search_indicators is called
    Then: Returns indicators with correct verdict based on score
    """
    args = {"value": json.dumps(["example"])}
    mock_indicators = [
        {"id": "2", "score": 1, "value": "unknown"},
        {"id": "3", "score": 2, "value": "suspicious"},
        {"id": "4", "score": 3, "value": "bad"},
    ]

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": mock_indicators}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert len(filtered_indicators) == 3
    assert filtered_indicators[0]["verdict"] == "Benign"
    assert filtered_indicators[1]["verdict"] == "Suspicious"
    assert filtered_indicators[2]["verdict"] == "Malicious"


def test_search_indicators_mixed_field_sources(mocker):
    """
    Given: Indicators with fields from both standard and CustomFields locations
    When: search_indicators is called
    Then: Returns indicators with proper field precedence (standard over CustomFields)
    """
    args = {"value": json.dumps(["example."])}
    mock_indicator = {
        "id": "123",
        "indicator_type": "Domain",
        "value": "example.",
        "score": 2,
        "CustomFields": {
            "indicator_type": "IP",
            "score": 3,
            "expirationStatus": "Expired",
            "investigationIDs": ["custom_inv"],
            "lastSeen": "2022-01-01",
        },
    }

    mock_demisto = mocker.patch("SearchIndicatorAgentix.demisto")
    mock_demisto.executeCommand.return_value = [{"Contents": [mock_indicator]}]
    mock_demisto.debug.return_value = None

    markdown, filtered_indicators = search_indicators(args)

    assert len(filtered_indicators) == 1
    assert filtered_indicators[0]["indicator_type"] == "Domain"
    assert filtered_indicators[0]["score"] == 2
    assert filtered_indicators[0]["expirationStatus"] == "Expired"