SOCRadarIncidentsMultiTenant

SOCRadar Multi-Tenant Incidents integration with advanced incident management, status reasons, and compliance tracking. Fetches security incidents from SOCRadar platform across all tenant companies with proper deduplication and date handling. Uses Multi-Tenant API for incident fetching and company-specific APIs for alarm management.

Analytics & SIEM · SOCRadar

Details

IDSOCRadarIncidentsMultiTenant
ProviderSOCRadar
CategoryAnalytics & SIEM
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

SOCRadar Incidents v4.0 Multi-Tenant

Fetch and manage security incidents from multiple companies using SOCRadar’s Multi-Tenant Incident API. Designed for MSPs, MSSPs, and organizations managing multiple subsidiaries.

Overview

SOCRadar is a digital risk protection platform that provides extended threat intelligence and brand protection capabilities. This Multi-Tenant integration enables XSOAR to ingest security incidents from multiple companies through a single integration instance, including:

  • Brand Protection: Impersonating domains, phishing attacks, brand abuse
  • Cyber Threat Intelligence: Stolen credentials, data leaks, malware infections
  • Attack Surface Management: External exposure findings, misconfigurations
  • Dark Web Intelligence: Compromised credentials, leaked data from dark web sources
  • Supply Chain Security: Third-party risks and vendor security issues

Multi-Tenant Features

Centralized Multi-Company Management

  • Single Integration: Monitor incidents from all your companies through one integration instance
  • Company Tracking: Each alarm automatically includes company ID and company name
  • Smart Filtering: Filter and manage incidents across companies or focus on specific ones

Automatic Company ID Handling

  • Auto-Extraction: When taking actions, company ID is automatically extracted from alarm data
  • No Manual Input: You don’t need to remember or specify company IDs for most operations
  • Override Capability: Manually specify company ID when needed (advanced use cases)

Company Visibility Control

  • Configurable Display: Choose whether to show company information in incident details
  • Custom Fields: Company ID and company name available in custom fields
  • Incident Naming: Company information included in incident names for quick identification

What’s New in Multi-Tenant v4.0

Multi-Tenant Specific

  • Multi-Tenant API Endpoint: Uses /multi_tenant/{multi-tenant-id}/incidents for fetching
  • Company Information: Each alarm includes both company_id and company_name
  • Smart Action Handling: Automatically determines which company to act upon
  • Default Company Visibility: Company info shown by default (can be disabled)

Core Features (from v4.0)

  • Multi-Status Filtering: Select multiple statuses (OPEN, CLOSED, ON_HOLD) simultaneously
  • Epoch Time Precision: Second-level accuracy for incident fetching - zero duplicates
  • Reverse Pagination: Fetches newest incidents first for better performance
  • Dynamic Content Extraction: Automatically extracts alarm-specific fields regardless of type
  • Enhanced Deduplication: Two-layer protection prevents duplicate incidents

Technical Improvements

  • Interval-based fetching with overlap protection
  • Configurable content and entity inclusion
  • Comprehensive debug logging
  • Better error handling and recovery
  • Intelligent company ID extraction from incident context

Key Differences: Standard vs Multi-Tenant

Feature Standard v4.0 Multi-Tenant v4.0
Configuration Company ID + API Key Multi-Tenant ID + API Key
Fetch Endpoint /company/{id}/incidents/v4 /multi_tenant/{id}/incidents
Company Data Single company (implicit) Multiple companies (explicit)
Company ID in Actions Uses configured company ID Auto-extracted from alarm
Company Visibility Optional (default: hidden) Optional (default: visible)
Use Case Single organization MSPs, MSSPs, multi-subsidiary

Prerequisites

Required

  • SOCRadar account with Multi-Tenant Incident API access
  • Multi-Tenant ID from SOCRadar platform
  • API Key from SOCRadar platform
  • XSOAR 6.x or later

API Access

To obtain your API credentials:

  1. Log in to SOCRadar Platform
  2. Reach out support team to get MSSP API Key

Configuration

Integration Settings

Parameter Required Default Description
Server URL Yes https://platform.socradar.com/api SOCRadar API base URL
API Key Yes - Your Multi-Tenant API Key from SOCRadar
Multi-Tenant ID Yes - Your Multi-Tenant ID (integer)
Fetch incidents No False Enable automatic incident fetching
Incident type No - XSOAR incident type to create
Max incidents per fetch No 10000 Maximum incidents per fetch cycle
First fetch time No 3 days Initial time range for first fetch
Fetch Interval (Minutes) No 1 Time window for subsequent fetches

Filtering Options

Parameter Type Description
Status Filter Multi-select Select one or more: OPEN, CLOSED, ON_HOLD
Severity Multi-select Filter by: LOW, MEDIUM, HIGH, CRITICAL
Alarm Type IDs Text Comma-separated list of type IDs to include
Excluded Alarm Type IDs Text Comma-separated list of type IDs to exclude
Main Alarm Types Text Comma-separated main types (e.g., “Brand Protection”)
Alarm Sub Types Text Comma-separated sub types

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.

socradar-change-alarm-status


Change the status of one or more alarms.

Base Command

socradar-change-alarm-status

Input

Argument Name Description Required
alarm_ids Comma-separated list of alarm IDs to update. Required
status_reason New status reason for the alarms. Possible values are: OPEN, INVESTIGATING, RESOLVED, PENDING_INFO, LEGAL_REVIEW, VENDOR_ASSESSMENT, FALSE_POSITIVE, DUPLICATE, PROCESSED_INTERNALLY, MITIGATED, NOT_APPLICABLE. Required
comments Optional comments explaining the status change. Optional
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.
SOCRadar.Alarm.Status String New alarm status.

socradar-mark-false-positive


Mark an alarm as false positive.

Base Command

socradar-mark-false-positive

Input

Argument Name Description Required
alarm_id Alarm ID to mark as false positive. Required
comments Optional comments explaining why this is a false positive. Default is False positive. Optional
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.
SOCRadar.Alarm.Status String New alarm status.

socradar-mark-resolved


Mark an alarm as resolved.

Base Command

socradar-mark-resolved

Input

Argument Name Description Required
alarm_id Alarm ID to mark as resolved. Required
comments Optional comments explaining the resolution. Default is Resolved. Optional
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.
SOCRadar.Alarm.Status String New alarm status.

socradar-add-comment


Add a comment to an alarm.

Base Command

socradar-add-comment

Input

Argument Name Description Required
alarm_id Alarm ID to add comment to. Required
user_email Email address of the user adding the comment. Required
comment Comment text to add. Required
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.

socradar-add-assignee


Add the assignee(s) of an alarm.

Base Command

socradar-add-assignee

Input

Argument Name Description Required
alarm_id Alarm ID to add assignee for. Required
user_emails Comma-separated list of user email addresses to assign. Required
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.
SOCRadar.Alarm.Assignees String New assignees.

socradar-add-tag


Add or remove a tag from an alarm.

Base Command

socradar-add-tag

Input

Argument Name Description Required
alarm_id Alarm ID to add/remove tag for. Required
tag Tag name to add or remove. Required
company_id Company ID for the alarm. If not provided, will be auto-fetched from alarm data. Can also use ${incident.socradarcompanyid} from incident fields. Optional

Context Output

Path Type Description
SOCRadar.Alarm.ID String Alarm ID.
SOCRadar.Alarm.Tags String Alarm tags.

socradar-test-fetch


Test incident fetching to verify alarms are available and date parsing works correctly.

Base Command

socradar-test-fetch

Input

Argument Name Description Required
limit Number of incidents to test fetch (default 5). Default is 5. Optional
first_fetch Test date range (e.g., “3 days”, “7 days”). Default is 3 days. Optional

Context Output

Path Type Description
SOCRadar.TestFetch.TotalCount Number Total number of incidents found.
SOCRadar.TestFetch.SampleIncidents Unknown Sample incidents for testing.
SOCRadar.TestFetch.StartDate String Parsed start date used for the test.
SOCRadar.TestFetch.TotalRecords Number Total number of incidents from service.
SOCRadar.TestFetch.TotalPages Number Total number of pages of incidents from service.

License

This integration is provided as part of the Cortex XSOAR content pack.

Configuration parameters

  • apikey — API Key (required)
  • multi_tenant_id — Multi-Tenant ID (required)
  • isFetch — Fetch incidents
  • first_fetch — First Fetch Time
  • fetch_interval_minutes — Fetch Interval (Minutes)
  • max_fetch — Max incidents per fetch
  • show_content — Show Alarm Content
  • status — Status Filter
  • severities — Severity Levels
  • alarm_type_ids — Alarm Type IDs (Include)
  • excluded_alarm_type_ids — Alarm Type IDs (Exclude)
  • alarm_main_types — Alarm Main Types
  • excluded_alarm_main_types — Alarm Main Types (Exclude)
  • alarm_sub_types — Alarm Sub Types
  • excluded_alarm_sub_types — Alarm Sub Types (Exclude)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval

Commands (7)

  • socradar-add-assignee

    Add the assignee(s) of an alarm.

  • socradar-add-comment

    Add a comment to an alarm.

  • socradar-add-tag

    Add or remove a tag from an alarm.

  • socradar-change-alarm-status

    Change the status of one or more alarms.

  • socradar-mark-false-positive

    Mark an alarm as false positive.

  • socradar-mark-resolved

    Mark an alarm as resolved.

  • socradar-test-fetch

    Test incident fetching to verify alarms are available and date parsing works correctly.

import pytest
import json
from CommonServerPython import *
from SOCRadarIncidentsV4MultiTenant import Client, STATUS_REASON_MAP


@pytest.fixture
def mock_client(mocker):
    """Create a mock client for testing"""
    client = Client(
        base_url="https://dummy-test.com/api",
        api_key="test_api_key",
        multi_tenant_id="test_tenant_id",
        verify=False,
        proxy=False,
    )
    return client


@pytest.fixture
def sample_alarm():
    """Sample alarm data for testing"""
    return {
        "alarm_id": 12345,
        "company_id": "67890",
        "alarm_risk_level": "HIGH",
        "alarm_asset": "test.com",
        "status": "OPEN",
        "date": "2024-01-15T10:30:00.000000",
        "alarm_type_details": {
            "alarm_main_type": "Brand Protection",
            "alarm_sub_type": "Impersonating Domain",
        },
        "alarm_text": "Suspicious domain detected",
        "tags": ["phishing", "urgent"],
        "alarm_related_entities": [
            {"key": "domain", "value": "fake-test.com"},
            {"key": "ip", "value": "192.168.1.1"},
        ],
    }


@pytest.mark.parametrize(
    "severity,expected",
    [
        pytest.param("LOW", IncidentSeverity.LOW, id="low_severity"),
        pytest.param("MEDIUM", IncidentSeverity.MEDIUM, id="medium_severity"),
        pytest.param("HIGH", IncidentSeverity.HIGH, id="high_severity"),
        pytest.param("CRITICAL", IncidentSeverity.CRITICAL, id="critical_severity"),
        pytest.param("INFO", IncidentSeverity.INFO, id="info_severity"),
        pytest.param("INVALID", IncidentSeverity.UNKNOWN, id="unknown_severity"),
    ],
)
def test_convert_to_demisto_severity(severity, expected):
    """Test severity conversion with various inputs"""
    from SOCRadarIncidentsV4MultiTenant import convert_to_demisto_severity

    assert convert_to_demisto_severity(severity) == expected


@pytest.mark.parametrize(
    "date_str,expected_year,expected_month,expected_day",
    [
        pytest.param("2024-01-15T10:30:00.123456", 2024, 1, 15, id="with_microseconds"),
        pytest.param("2024-01-15T10:30:00", 2024, 1, 15, id="without_microseconds"),
        pytest.param("2024-01-15", 2024, 1, 15, id="date_only"),
    ],
)
def test_parse_alarm_date_valid(date_str, expected_year, expected_month, expected_day):
    """Test parsing valid alarm dates"""
    from SOCRadarIncidentsV4MultiTenant import parse_alarm_date

    result = parse_alarm_date(date_str)
    assert result is not None
    assert result.year == expected_year
    assert result.month == expected_month
    assert result.day == expected_day


@pytest.mark.parametrize(
    "date_str",
    [
        pytest.param(None, id="none_date"),
        pytest.param("invalid-date", id="invalid_format"),
    ],
)
def test_parse_alarm_date_invalid(date_str):
    """Test parsing invalid alarm dates"""
    from SOCRadarIncidentsV4MultiTenant import parse_alarm_date

    result = parse_alarm_date(date_str)
    assert result is None


def test_alarm_to_incident_basic(sample_alarm):
    """Test basic alarm to incident conversion"""
    from SOCRadarIncidentsV4MultiTenant import alarm_to_incident

    incident = alarm_to_incident(sample_alarm)

    assert incident["name"] == "SOCRadar Alarm 12345: Brand Protection - Impersonating Domain [test.com]"
    assert incident["severity"] == IncidentSeverity.HIGH
    assert incident["dbotMirrorId"] == "12345"
    assert "12345" in incident["details"]
    assert sample_alarm["alarm_asset"] in incident["details"]


def test_alarm_to_incident_custom_fields(sample_alarm):
    """Test custom fields in alarm to incident conversion"""
    from SOCRadarIncidentsV4MultiTenant import alarm_to_incident

    incident = alarm_to_incident(sample_alarm)

    assert incident["CustomFields"]["socradaralarmid"] == "12345"
    assert incident["CustomFields"]["socradarcompanyid"] == "67890"
    assert incident["CustomFields"]["socradarstatus"] == "OPEN"
    assert incident["CustomFields"]["socradarasset"] == "test.com"
    assert incident["CustomFields"]["socradaralarmtype"] == "Brand Protection"
    assert "phishing" in incident["CustomFields"]["socradartags"]


def test_alarm_to_incident_with_missing_fields():
    """Test alarm to incident conversion with minimal data"""
    from SOCRadarIncidentsV4MultiTenant import alarm_to_incident

    minimal_alarm = {
        "alarm_id": 999,
        "company_id": "123",
    }
    incident = alarm_to_incident(minimal_alarm)

    assert incident["name"] is not None
    assert incident["CustomFields"]["socradaralarmid"] == "999"
    assert incident["CustomFields"]["socradarcompanyid"] == "123"


def test_alarm_to_incident_raw_json(sample_alarm):
    """Test raw JSON in alarm to incident conversion"""
    from SOCRadarIncidentsV4MultiTenant import alarm_to_incident

    incident = alarm_to_incident(sample_alarm)
    raw_json = json.loads(incident["rawJSON"])

    assert raw_json["alarm_id"] == 12345
    assert raw_json["company_id"] == "67890"


class TestClient:
    """Test Client class methods"""

    def test_client_initialization(self):
        """Test client initialization"""
        client = Client(
            base_url="https://dummy-test.com/api",
            api_key="test_key",
            multi_tenant_id="tenant_123",
            verify=True,
            proxy=False,
        )

        assert client.api_key == "test_key"
        assert client.multi_tenant_id == "tenant_123"

    def test_get_headers(self, mock_client):
        """Test headers generation"""
        headers = mock_client._get_headers()
        assert headers["API-Key"] == "test_api_key"

    def test_search_incidents_params(self, mock_client, mocker):
        """Test search incidents with parameters"""
        mock_response = {
            "is_success": True,
            "message": "Success",
            "data": {"alarms": [], "total_pages": 1, "total_records": 0},
        }

        mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

        result = mock_client.search_incidents(
            status=["OPEN"],
            severities=["HIGH"],
            limit=50,
            page=1,
        )

        assert result["is_success"] is True
        assert result["data"] == []
        assert result["total_pages"] == 1

    def test_change_alarm_status_invalid_status(self, mock_client):
        """Test change alarm status with invalid status"""
        with pytest.raises(ValueError, match="Invalid status reason"):
            mock_client.change_alarm_status([123], "INVALID_STATUS", company_id="123")

    def test_change_alarm_status_missing_company_id(self, mock_client):
        """Test change alarm status without company ID"""
        with pytest.raises(ValueError, match="company_id must be provided"):
            mock_client.change_alarm_status([123], "OPEN")


def test_change_status_command_success(mock_client, mocker):
    """Test change status command success"""
    from SOCRadarIncidentsV4MultiTenant import change_status_command

    mock_response = {"is_success": True, "message": "Status changed"}
    mocker.patch.object(mock_client, "change_alarm_status", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {
        "alarm_ids": "123,456",
        "status_reason": "RESOLVED",
        "comments": "Test comment",
    }

    result = change_status_command(mock_client, args)
    assert "Status changed for 2 alarm(s)" in result.readable_output


def test_change_status_command_missing_params(mock_client):
    """Test change status command with missing parameters"""
    from SOCRadarIncidentsV4MultiTenant import change_status_command

    args = {"alarm_ids": "123"}

    with pytest.raises(ValueError, match="alarm_ids and status_reason are required"):
        change_status_command(mock_client, args)


def test_mark_false_positive_success(mock_client, mocker):
    """Test mark as false positive command success"""
    from SOCRadarIncidentsV4MultiTenant import mark_as_false_positive_command

    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "change_alarm_status", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123", "comments": "False positive"}

    result = mark_as_false_positive_command(mock_client, args)
    assert "marked as false positive" in result.readable_output


def test_mark_false_positive_missing_alarm_id(mock_client):
    """Test mark as false positive with missing alarm ID"""
    from SOCRadarIncidentsV4MultiTenant import mark_as_false_positive_command

    args = {}

    with pytest.raises(ValueError, match="alarm_id is required"):
        mark_as_false_positive_command(mock_client, args)


def test_mark_resolved_success(mock_client, mocker):
    """Test mark as resolved command success"""
    from SOCRadarIncidentsV4MultiTenant import mark_as_resolved_command

    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "change_alarm_status", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123"}

    result = mark_as_resolved_command(mock_client, args)
    assert "marked as resolved" in result.readable_output


def test_add_comment_success(mock_client, mocker):
    """Test add comment command success"""
    from SOCRadarIncidentsV4MultiTenant import add_comment_command

    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "add_alarm_comment", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {
        "alarm_id": "123",
        "user_email": "test@example.com",
        "comment": "Test comment",
    }

    result = add_comment_command(mock_client, args)
    assert "Comment added to alarm 123" in result.readable_output


def test_add_comment_missing_params(mock_client, mocker):
    """Test add comment with missing parameters"""
    from SOCRadarIncidentsV4MultiTenant import add_comment_command

    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123"}

    with pytest.raises(ValueError, match="user_email and comment are required"):
        add_comment_command(mock_client, args)


def test_add_assignee_success(mock_client, mocker):
    """Test add assignee command success"""
    from SOCRadarIncidentsV4MultiTenant import add_assignee_command

    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "add_alarm_assignee", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {
        "alarm_id": "123",
        "user_emails": "user1@example.com,user2@example.com",
    }

    result = add_assignee_command(mock_client, args)
    assert "Assignee added for alarm 123" in result.readable_output


def test_add_assignee_missing_emails(mock_client, mocker):
    """Test add assignee with missing emails"""
    from SOCRadarIncidentsV4MultiTenant import add_assignee_command

    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123"}

    with pytest.raises(ValueError, match="user_emails is required"):
        add_assignee_command(mock_client, args)


def test_add_tag_success(mock_client, mocker):
    """Test add tag command success"""
    from SOCRadarIncidentsV4MultiTenant import add_tag_command

    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "add_remove_tag", return_value=mock_response)
    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123", "tag": "urgent"}

    result = add_tag_command(mock_client, args)
    assert "Tag 'urgent' added/removed for alarm 123" in result.readable_output


def test_add_tag_missing_tag(mock_client, mocker):
    """Test add tag with missing tag"""
    from SOCRadarIncidentsV4MultiTenant import add_tag_command

    mocker.patch.object(mock_client, "get_company_id_for_alarm", return_value="123")

    args = {"alarm_id": "123"}

    with pytest.raises(ValueError, match="tag is required"):
        add_tag_command(mock_client, args)


def test_test_fetch_success(mock_client, mocker):
    """Test the test-fetch command success"""
    from SOCRadarIncidentsV4MultiTenant import test_fetch_command

    mock_response = {
        "is_success": True,
        "data": [
            {
                "alarm_id": 123,
                "company_id": "456",
                "alarm_risk_level": "HIGH",
                "status": "OPEN",
                "alarm_asset": "test.com",
                "date": "2024-01-15T10:30:00",
                "alarm_type_details": {
                    "alarm_main_type": "Test Type",
                    "alarm_sub_type": "Test Sub",
                },
                "alarm_related_entities": [],
            }
        ],
        "total_records": 1,
        "total_pages": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    args = {"limit": "5", "first_fetch": "3 days"}

    result = test_fetch_command(mock_client, args)
    assert "Found 1 incident(s)" in result.readable_output


def test_test_fetch_no_incidents(mock_client, mocker):
    """Test the test-fetch command with no incidents"""
    from SOCRadarIncidentsV4MultiTenant import test_fetch_command

    mock_response = {
        "is_success": True,
        "data": [],
        "total_records": 0,
        "total_pages": 0,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    args = {"limit": "5", "first_fetch": "3 days"}

    result = test_fetch_command(mock_client, args)
    assert "No incidents found" in result.readable_output


def test_status_reason_map_completeness():
    """Test that all expected status reasons are in the map"""
    expected_statuses = [
        "OPEN",
        "INVESTIGATING",
        "RESOLVED",
        "PENDING_INFO",
        "LEGAL_REVIEW",
        "VENDOR_ASSESSMENT",
        "FALSE_POSITIVE",
        "DUPLICATE",
        "PROCESSED_INTERNALLY",
        "MITIGATED",
        "NOT_APPLICABLE",
    ]

    for status in expected_statuses:
        assert status in STATUS_REASON_MAP
        assert isinstance(STATUS_REASON_MAP[status], int)


def test_test_module_success(mock_client, mocker, capfd):
    """Test the test-module command success"""
    from SOCRadarIncidentsV4MultiTenant import test_module

    mock_response = {
        "is_success": True,
        "data": [{"alarm_id": 123, "company_id": "456"}],
    }
    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    with capfd.disabled():
        result = test_module(mock_client)
    assert result == "ok"


def test_test_module_failure(mock_client, mocker, capfd):
    """Test the test-module command failure"""
    from SOCRadarIncidentsV4MultiTenant import test_module

    mock_response = {
        "is_success": False,
        "message": "Authentication failed",
    }
    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    with capfd.disabled():
        result = test_module(mock_client)
    assert "Test failed" in result


def test_test_module_unauthorized(mock_client, mocker, capfd):
    """Test the test-module command with 401 error"""
    from SOCRadarIncidentsV4MultiTenant import test_module

    mocker.patch.object(
        mock_client,
        "search_incidents",
        side_effect=DemistoException("401 Unauthorized"),
    )

    with capfd.disabled():
        result = test_module(mock_client)
    assert "Authorization Error" in result


def test_test_module_forbidden(mock_client, mocker, capfd):
    """Test the test-module command with 403 error"""
    from SOCRadarIncidentsV4MultiTenant import test_module

    mocker.patch.object(
        mock_client,
        "search_incidents",
        side_effect=DemistoException("403 Forbidden"),
    )

    with capfd.disabled():
        result = test_module(mock_client)
    assert "Access Denied" in result


def test_test_module_not_found(mock_client, mocker, capfd):
    """Test the test-module command with 404 error"""
    from SOCRadarIncidentsV4MultiTenant import test_module

    mocker.patch.object(
        mock_client,
        "search_incidents",
        side_effect=DemistoException("404 Not Found"),
    )

    with capfd.disabled():
        result = test_module(mock_client)
    assert "API Endpoint Not Found" in result


def test_fetch_incidents_first_fetch(mock_client, mocker):
    """Test fetch incidents on first run"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mock_response = {
        "is_success": True,
        "data": [
            {
                "alarm_id": 123,
                "company_id": "456",
                "alarm_risk_level": "HIGH",
                "alarm_asset": "test.com",
                "status": "OPEN",
                "date": "2024-01-15T10:30:00",
                "alarm_type_details": {
                    "alarm_main_type": "Test Type",
                    "alarm_sub_type": "Test Sub",
                },
                "alarm_text": "Test alarm",
                "tags": [],
                "alarm_related_entities": [],
            }
        ],
        "total_records": 1,
        "total_pages": 1,
        "current_page": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    assert len(incidents) == 1
    assert "last_fetch" in next_run
    assert "last_alarm_ids" in next_run
    assert incidents[0]["name"] is not None


def test_fetch_incidents_subsequent_fetch(mock_client, mocker):
    """Test fetch incidents on subsequent runs"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents
    from datetime import datetime, timedelta

    last_fetch_time = (datetime.now() - timedelta(minutes=5)).isoformat() + "Z"

    mock_response = {
        "is_success": True,
        "data": [],
        "total_records": 0,
        "total_pages": 0,
        "current_page": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={"last_fetch": last_fetch_time, "last_alarm_ids": [123, 456]},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    assert len(incidents) == 0
    assert "last_fetch" in next_run


def test_fetch_incidents_with_duplicates(mock_client, mocker):
    """Test fetch incidents with duplicate detection"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents
    from datetime import datetime, timedelta

    last_fetch_time = (datetime.now() - timedelta(minutes=5)).isoformat() + "Z"

    mock_response = {
        "is_success": True,
        "data": [
            {
                "alarm_id": 123,  # This is a duplicate
                "company_id": "456",
                "alarm_risk_level": "HIGH",
                "alarm_asset": "test.com",
                "status": "OPEN",
                "date": "2024-01-15T10:30:00",
                "alarm_type_details": {
                    "alarm_main_type": "Test Type",
                    "alarm_sub_type": "Test Sub",
                },
                "alarm_text": "Test alarm",
                "tags": [],
                "alarm_related_entities": [],
            }
        ],
        "total_records": 1,
        "total_pages": 1,
        "current_page": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={"last_fetch": last_fetch_time, "last_alarm_ids": [123]},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    # Should skip the duplicate
    assert len(incidents) == 0


def test_fetch_incidents_with_filters(mock_client, mocker):
    """Test fetch incidents with various filters"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mock_response = {
        "is_success": True,
        "data": [],
        "total_records": 0,
        "total_pages": 0,
        "current_page": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
        status=["OPEN"],
        severities=["HIGH", "CRITICAL"],
        alarm_type_ids=[1, 2, 3],
        excluded_alarm_type_ids=[4, 5],
        alarm_main_types=["Brand Protection"],
        excluded_alarm_main_types=["Test Type"],
        alarm_sub_types=["Phishing"],
        excluded_alarm_sub_types=["Test Sub"],
    )

    assert "last_fetch" in next_run


def test_fetch_incidents_error_handling(mock_client, mocker, capfd):
    """Test fetch incidents error handling"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mocker.patch.object(
        mock_client,
        "search_incidents",
        side_effect=Exception("API Error"),
    )

    with capfd.disabled():
        next_run, incidents = fetch_incidents(
            client=mock_client,
            max_results=100,
            last_run={},
            first_fetch_time="3 days",
            fetch_interval_minutes=1,
        )

    # Should return empty incidents on error
    assert len(incidents) == 0
    assert "last_fetch" in next_run


def test_client_add_alarm_comment(mock_client, mocker):
    """Test client add alarm comment method"""
    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    result = mock_client.add_alarm_comment(
        alarm_id=123,
        user_email="test@example.com",
        comment="Test comment",
        company_id="456",
    )

    assert result["is_success"] is True


def test_client_add_alarm_assignee(mock_client, mocker):
    """Test client add alarm assignee method"""
    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    result = mock_client.add_alarm_assignee(
        alarm_id=123,
        user_emails=["user1@example.com", "user2@example.com"],
        company_id="456",
    )

    assert result["is_success"] is True


def test_client_add_remove_tag(mock_client, mocker):
    """Test client add/remove tag method"""
    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    result = mock_client.add_remove_tag(
        alarm_id=123,
        tag="urgent",
        company_id="456",
    )

    assert result["is_success"] is True


def test_client_get_company_id_for_alarm(mock_client, mocker):
    """Test client get company ID for alarm method"""
    mock_response = {
        "is_success": True,
        "data": [{"alarm_id": 123, "company_id": "456"}],
        "total_pages": 1,
    }
    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    result = mock_client.get_company_id_for_alarm(123)

    assert result == "456"


def test_client_get_company_id_for_alarm_not_found(mock_client, mocker):
    """Test client get company ID for alarm when not found"""
    mock_response = {
        "is_success": True,
        "data": [],
        "total_pages": 0,
    }
    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    result = mock_client.get_company_id_for_alarm(999)

    assert result is None


def test_client_change_alarm_status_success(mock_client, mocker):
    """Test client change alarm status success"""
    mock_response = {"is_success": True}
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    result = mock_client.change_alarm_status(
        alarm_ids=[123, 456],
        status_reason="RESOLVED",
        comments="Test",
        company_id="789",
    )

    assert result["is_success"] is True


def test_client_change_alarm_status_api_error(mock_client, mocker):
    """Test client change alarm status with API error"""
    mock_response = {"is_success": False, "message": "API Error"}
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    with pytest.raises(DemistoException, match="API Error"):
        mock_client.change_alarm_status(
            alarm_ids=[123],
            status_reason="RESOLVED",
            company_id="789",
        )


def test_client_search_incidents_error_response(mock_client, mocker, capfd):
    """Test client search incidents with error response"""
    mock_response = {
        "is_success": False,
        "message": "Invalid request",
    }
    mocker.patch.object(mock_client, "_http_request", return_value=mock_response)

    with capfd.disabled(), pytest.raises(DemistoException, match="Invalid request"):
        mock_client.search_incidents(limit=10, page=1)


def test_fetch_incidents_no_alarms(mock_client, mocker):
    """Test fetch when API returns no alarms"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mocker.patch.object(
        mock_client,
        "search_incidents",
        return_value={
            "is_success": True,
            "data": [],
            "total_records": 0,
            "total_pages": 0,
            "current_page": 1,
        },
    )

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    assert len(incidents) == 0
    assert "last_fetch" in next_run
    assert "last_alarm_ids" in next_run


def test_fetch_incidents_partial_page(mock_client, mocker):
    """Test fetch stops on partial page"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mock_response = {
        "is_success": True,
        "data": [
            {
                "alarm_id": 999,
                "company_id": "111",
                "alarm_risk_level": "LOW",
                "alarm_asset": "example.com",
                "status": "OPEN",
                "date": "2024-01-15T10:30:00",
                "alarm_type_details": {"alarm_main_type": "Test", "alarm_sub_type": ""},
                "alarm_text": "test",
                "tags": [],
                "alarm_related_entities": [],
            }
        ],
        "total_records": 1,
        "total_pages": 5,
        "current_page": 1,
    }

    mocker.patch.object(mock_client, "search_incidents", return_value=mock_response)

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={"last_fetch": "2024-01-14T00:00:00Z"},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    assert len(incidents) == 1
    assert next_run["last_alarm_ids"] == [999]


def test_fetch_incidents_multi_page(mock_client, mocker):
    """Test fetch collects incidents across multiple pages"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    alarm_template = {
        "company_id": "111",
        "alarm_risk_level": "HIGH",
        "alarm_asset": "example.com",
        "status": "OPEN",
        "date": "2024-01-15T10:30:00",
        "alarm_type_details": {"alarm_main_type": "Test", "alarm_sub_type": ""},
        "alarm_text": "test",
        "tags": [],
        "alarm_related_entities": [],
    }

    page1 = {
        "is_success": True,
        "data": [{**alarm_template, "alarm_id": i} for i in range(1, 6)],
        "total_records": 10,
        "total_pages": 2,
        "current_page": 1,
    }
    page2 = {
        "is_success": True,
        "data": [{**alarm_template, "alarm_id": i} for i in range(6, 11)],
        "total_records": 10,
        "total_pages": 2,
        "current_page": 2,
    }

    mocker.patch.object(mock_client, "search_incidents", side_effect=[page1, page2])

    next_run, incidents = fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={"last_fetch": "2024-01-14T00:00:00Z"},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
    )

    assert len(incidents) >= 5
    assert "last_alarm_ids" in next_run


def test_fetch_incidents_severity_and_status_filter(mock_client, mocker):
    """Test fetch passes severity and status filters to search_incidents"""
    from SOCRadarIncidentsV4MultiTenant import fetch_incidents

    mock_search = mocker.patch.object(
        mock_client,
        "search_incidents",
        return_value={
            "is_success": True,
            "data": [],
            "total_records": 0,
            "total_pages": 0,
            "current_page": 1,
        },
    )

    fetch_incidents(
        client=mock_client,
        max_results=100,
        last_run={},
        first_fetch_time="3 days",
        fetch_interval_minutes=1,
        severities=["CRITICAL"],
        status=["OPEN", "INVESTIGATING"],
    )

    call_kwargs = mock_search.call_args[1]
    assert call_kwargs["severities"] == ["CRITICAL"]
    assert call_kwargs["status"] == ["OPEN", "INVESTIGATING"]