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
| ID | SOCRadarIncidentsMultiTenant |
|---|---|
| Provider | SOCRadar |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix 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}/incidentsfor 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:
- Log in to SOCRadar Platform
- 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 incidentsfirst_fetch— First Fetch Timefetch_interval_minutes— Fetch Interval (Minutes)max_fetch— Max incidents per fetchshow_content— Show Alarm Contentstatus— Status Filterseverities— Severity Levelsalarm_type_ids— Alarm Type IDs (Include)excluded_alarm_type_ids— Alarm Type IDs (Exclude)alarm_main_types— Alarm Main Typesexcluded_alarm_main_types— Alarm Main Types (Exclude)alarm_sub_types— Alarm Sub Typesexcluded_alarm_sub_types— Alarm Sub Types (Exclude)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Interval
Commands (7)
-
socradar-add-assigneeAdd the assignee(s) of an alarm.
-
socradar-add-commentAdd a comment to an alarm.
-
socradar-add-tagAdd or remove a tag from an alarm.
-
socradar-change-alarm-statusChange the status of one or more alarms.
-
socradar-mark-false-positiveMark an alarm as false positive.
-
socradar-mark-resolvedMark an alarm as resolved.
-
socradar-test-fetchTest 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"]