SysdigResponseActions

Uses the Sysdig agent to respond to malicious activity by triggering different actions at the host or container level, such as killing a container, quarantining a file, or performing a system capture.

Data Enrichment & Threat Intelligence · Sysdig Response Actions

Details

IDSysdigResponseActions
ProviderSysdig
CategoryData Enrichment & Threat Intelligence
From Version6.10.0
To Version99.99.99
Docker Imagedemisto/python3:3.12.13.10116658

README

This is an integration that will use Sysdig agent to respond to malicious activity by triggering different actions at the host or container level like killing a container, quarantine a file or perform a system capture
This integration was integrated and tested with Host shield 13.9.1 of the Sysdig Agent and ResponseActions version 0.1.0

This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.

Configure Sysdig Response Actions in Cortex

Parameter Required
Your server URL True
API Key True
Trust any certificate (not secure) False
Use system proxy settings False
Classifier False
Incident type (if classifier doesn’t exist) False
Mapper (incoming) False

Commands

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

execute-response-action


Executes response actions through the Sysdig API. Each action type requires a specific set of parameters.

Base Command

execute-response-action

Input

Argument Name Description Required
actionType The action type to perform. Required parameters per action:
- KILL_PROCESS: host_id, process_id (startTime defaults to -1)
- KILL_CONTAINER: host_id, container_id
- STOP_CONTAINER: host_id, container_id
- PAUSE_CONTAINER: host_id, container_id
- UNPAUSE_CONTAINER: host_id, container_id
- START_CONTAINER: host_id, container_id
- FILE_QUARANTINE: host_id, path_absolute (optional: container_id)
- FILE_ACQUIRE: host_id, path_absolute (optional: container_id)
- FILE_UNQUARANTINE: host_id, path_absolute, quarantined_file_path (optional: container_id)
- DELETE_POD: k8s_cluster_name, k8s_namespace_name, k8s_pod_name
- ROLLOUT_RESTART: k8s_cluster_name, k8s_namespace_name, k8s_workload_type, k8s_workload_name
- ISOLATE_NETWORK: k8s_cluster_name, k8s_namespace_name, k8s_workload_type, k8s_workload_name (optional: network_protocol, network_port, network_cidr, network_direction)
- DELETE_NETWORK_POLICY: k8s_cluster_name, k8s_namespace_name, network_policy_name
- GET_LOGS: k8s_cluster_name, k8s_namespace_name (optional: k8s_workload_type, k8s_workload_name, k8s_pod_name, k8s_container_name, previous, all_containers)
- KUBERNETES_VOLUME_SNAPSHOT: k8s_cluster_name, k8s_namespace_name (optional: k8s_pvc_name, k8s_workload_type, k8s_workload_name)
- KUBERNETES_DELETE_VOLUME_SNAPSHOT: k8s_cluster_name, k8s_namespace_name, k8s_pvc_name, k8s_volume_snapshot_name
- CAPTURE: host_id, capture_storage_config_id, capture_duration_ns, capture_past_duration_ns (optional: capture_filters, capture_max_size, container_id, capture_token)
- IAM_QUARANTINE: cloud_provider, cloud_account_id (optional: ct_user_arn, ct_user_identity_type, ct_user)
- IAM_UNQUARANTINE: cloud_provider, cloud_account_id, iam_policy_name, ct_user_identity_type, ct_user
- MAKE_PRIVATE_CLOUD_RESOURCE: cloud_provider, cloud_account_id, cloud_resource_type, cloud_resource_name (optional: cloud_region)
- UNDO_MAKE_PRIVATE_CLOUD_RESOURCE: cloud_provider, cloud_account_id, cloud_resource_type, cloud_resource_name, previous_public_access_settings (optional: cloud_region)
- CLOUD_VOLUME_SNAPSHOT: cloud_provider, cloud_account_id, cloud_region, aws_instance_id
- UNDO_CLOUD_VOLUME_SNAPSHOT: cloud_provider, cloud_account_id, cloud_region, snapshot_ids, aws_instance_id
- FETCH_CLOUD_LOGS: cloud_provider, cloud_account_id, cloud_region, from_timestamp, to_timestamp (optional: ct_original_user, ct_name, ct_src). Possible values are: KILL_PROCESS, KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER, FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT, CAPTURE, IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS.
Required
callerId The unique caller identifier for the audit trail. Required
host_id The host ID where the agent runs. Required for: KILL_PROCESS, KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER, FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, CAPTURE. Optional
container_id The container ID. Required for: KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER. Optional for: FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, CAPTURE. Optional
process_id The process ID to kill. Required for: KILL_PROCESS. Optional
path_absolute The absolute file path. Required for: FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE. Optional
quarantined_file_path The path of the quarantined file. Required for: FILE_UNQUARANTINE. Optional
k8s_cluster_name The Kubernetes cluster name. Required for: DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT. Optional
k8s_namespace_name The Kubernetes namespace. Required for: DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT. Optional
k8s_pod_name The Kubernetes pod name. Required for: DELETE_POD. Optional for: GET_LOGS. Optional
k8s_workload_type The Kubernetes workload type (for example, Deployment, StatefulSet, DaemonSet). Required for: ROLLOUT_RESTART, ISOLATE_NETWORK. Optional for: GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT. Optional
k8s_workload_name The Kubernetes workload name. Required for: ROLLOUT_RESTART, ISOLATE_NETWORK. Optional for: GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT. Optional
k8s_pvc_name The PVC name. Optional for: KUBERNETES_VOLUME_SNAPSHOT. Required for: KUBERNETES_DELETE_VOLUME_SNAPSHOT. Optional
k8s_volume_snapshot_name The volume snapshot name. Required for: KUBERNETES_DELETE_VOLUME_SNAPSHOT. Optional
k8s_container_name The container name for log retrieval. Optional for: GET_LOGS. Optional
network_policy_name The network policy name. Required for: DELETE_NETWORK_POLICY. Optional
network_protocol The network protocol (TCP, UDP). Optional for: ISOLATE_NETWORK. Optional
network_port The port number. Optional for: ISOLATE_NETWORK. Optional
network_cidr The CIDR range. Optional for: ISOLATE_NETWORK. Optional
network_direction The traffic direction (ingress, egress). Optional for: ISOLATE_NETWORK. Optional
previous Whether to retrieve logs from the previous container instance. Optional for: GET_LOGS. Optional
all_containers Whether to retrieve logs from all containers. Optional for: GET_LOGS. Optional
capture_storage_config_id The remote storage configuration ID. Required for: CAPTURE. Optional
capture_duration_ns The capture duration in nanoseconds. Required for: CAPTURE. Optional
capture_past_duration_ns The amount of time to capture retroactively, in nanoseconds. Required for: CAPTURE. Optional
capture_filters The syscall filter expression. Optional for: CAPTURE. Optional
capture_max_size The maximum capture file size in bytes (0 = no limit). Optional for: CAPTURE. Optional
capture_token The base name of the capture file. Optional for: CAPTURE. Optional
cloud_provider The cloud provider name (for example, aws). Required for: IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. Optional
cloud_account_id The cloud account ID. Required for: IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. Optional
cloud_region The cloud region. Required for: CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. Optional for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. Optional
ct_user_arn The AWS identity ARN. Optional for: IAM_QUARANTINE. Optional
ct_user_identity_type The AWS identity type (IAMUser, Role, AssumedRole). Required for: IAM_UNQUARANTINE. Optional for: IAM_QUARANTINE. Optional
ct_user The AWS identity name. Required for: IAM_UNQUARANTINE. Optional for: IAM_QUARANTINE. Optional
ct_original_user The CloudTrail original user. Optional for: FETCH_CLOUD_LOGS. Optional
ct_name The CloudTrail event name. Optional for: FETCH_CLOUD_LOGS. Optional
ct_src The CloudTrail source. Optional for: FETCH_CLOUD_LOGS. Optional
iam_policy_name The IAM policy name to remove. Required for: IAM_UNQUARANTINE. Optional
cloud_resource_type The cloud resource type (rds, s3). Required for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. Optional
cloud_resource_name The cloud resource name. Required for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. Optional
previous_public_access_settings The previous public access settings. Required for: UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. Optional
aws_instance_id The AWS instance ID. Required for: CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT. Optional
snapshot_ids The comma-separated snapshot IDs. Required for: UNDO_CLOUD_VOLUME_SNAPSHOT. Optional
from_timestamp The start timestamp for the log search. Required for: FETCH_CLOUD_LOGS. Optional
to_timestamp The end timestamp for the log search. Required for: FETCH_CLOUD_LOGS. Optional

Context Output

Path Type Description
execute_response_action.Output Dict The output of the response-actions API.

create-system-capture


Triggers a system capture, recording all system calls at the host level.

Base Command

create-system-capture

Input

Argument Name Description Required
container_id The container ID to apply the action. Example “container.id”: “123456789123”. Required
host_name The host name. Example “ip-1-1-1-1.us-west-1.compute.internal”. Required
capture_name The capture name. Required
agent_id The agent ID. Required
customer_id The customer ID. Required
machine_id The machine ID/MAC. Example “01:aa:02:bb:03:cc”. Required
scan_duration The capture duration in seconds. Optional
scap_filter The filter for the scope of the capture to take. Example: (proc.name=ncat or proc.name=vi). Optional

Context Output

Path Type Description
create_system_capture.Output Dict The output of the created system capture.

get-capture-file


Gets a system capture based on the capture ID.

Base Command

get-capture-file

Input

Argument Name Description Required
capture_id The system capture ID. Required

Context Output

Path Type Description
get_capture_file.Output Dict The output of the downloaded system capture.

get-action-execution


Gets the status and information of a triggered action execution.

Base Command

get-action-execution

Input

Argument Name Description Required
action_execution_id The action execution ID. Required

Context Output

Path Type Description
get_action_execution.Output Dict The output of the action execution info.

sysdig-agent-info-get


Resolves a host MAC address to its Sysdig agent details (agent ID, customer ID, hostname). The result is cached per MAC address.

Base Command

sysdig-agent-info-get

Input

Argument Name Description Required
machine_id The MAC address (machineId) of the host to look up. Required
force_refresh Whether to force a refresh of the cached agent info from the Sysdig API. Optional

Context Output

Path Type Description
Sysdig.Agent.agentId String The Sysdig agent ID.
Sysdig.Agent.customerId String The Sysdig customer ID.
Sysdig.Agent.hostName String The agent hostname.
Sysdig.Agent.machineId String The host MAC address.
Sysdig.Agent.hostId String The opaque host identifier.
Sysdig.Agent.clusterName String The Kubernetes cluster name.

sysdig-customer-info-get


Gets the Sysdig customer ID and name. The result is cached per integration instance.

Base Command

sysdig-customer-info-get

Input

Argument Name Description Required
force_refresh Whether to force a refresh of the cached customer info from the Sysdig API. Optional

Context Output

Path Type Description
Sysdig.Customer.customerId String The Sysdig customer ID.
Sysdig.Customer.customerName String The Sysdig customer name.

Configuration parameters

  • url — Your server URL (required)
  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • classifier — Classifier
  • mapper — Mapper (incoming)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • max_fetch — The maximum number of incidents per fetch
  • first_fetch — First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)

Commands (6)

  • create-system-capture

    Triggers a system capture, recording all system calls at the host level.

  • execute-response-action

    Executes response actions through the Sysdig API. Each action type requires a specific set of parameters. Use the actionType description to see which parameters are required for each action.

  • get-action-execution

    Gets the status and information of a triggered action execution.

  • get-capture-file

    Gets a system capture based on the capture ID.

  • sysdig-agent-info-get

    Resolves a host MAC address to its Sysdig agent details (agent ID, customer ID, hostname). The result is cached per MAC address.

  • sysdig-customer-info-get

    Gets the Sysdig customer ID and name. The result is cached per integration instance.

"""
Sysdig Response Actions Integration - Unit Tests file
"""

import pytest
from http import HTTPStatus
import requests
from unittest.mock import patch
import demistomock as demisto  # noqa: F401

from SysdigResponseActions import (
    Client,
    execute_response_action_command,
    create_system_capture_command,
    get_capture_file_command,
    get_action_execution_command,
    get_agent_by_mac_command,
    get_customer_info_command,
    _cache_is_valid,
)

import json
import time


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


KILL_CONTAINER_RESPONSE = util_load_json("test_data/response_api/kill_container_response.json")
GET_ACTION_EXECUTION_RESPONSE = util_load_json("test_data/response_api/get_execution_response.json")
SYSTEM_CAPTURE_RESPONSE = util_load_json("test_data/system_capture/create_response.json")
GET_AGENTS_CONNECTED_RESPONSE = util_load_json("test_data/response_api/get_agents_connected_response.json")
GET_USERS_ME_RESPONSE = util_load_json("test_data/response_api/get_users_me_response.json")


@pytest.fixture
def mock_response():
    """Fixture to mock the session.request method"""
    with patch.object(requests.Session, "request") as mock_request:
        yield mock_request


def test_execute_response_action(mock_response):
    # Successful response
    mock_response.return_value.json.return_value = KILL_CONTAINER_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = execute_response_action_command(
        client,
        {
            "actionType": "KILL_CONTAINER",
            "container_id": "123456789012",
            "host_id": "abc123def456",
            "callerId": "test_kill_container_07",
        },
    )
    result = result.to_context().get("Contents")

    assert result.get("actionType") == "KILL_CONTAINER"
    assert result.get("status") == "created"
    assert result.get("callerId") == "test_kill_container_07"
    assert result.get("executionContext").get("container.id") == "123456789012"


def test_create_system_capture(mock_response):
    # Successful response
    mock_response.return_value.json.return_value = SYSTEM_CAPTURE_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = create_system_capture_command(
        client,
        {
            "host_name": "ip-1-2-3-4.us-west-1.compute.internal",
            "container_id": "123456789012",
            "capture_name": "test_capture",
            "agent_id": "123456789012",
            "customer_id": "123456789012",
            "machine_id": "aa:bb:cc:11:22:33",
        },
    )
    result = result.to_context().get("Contents").get("capture")

    assert result.get("status") == "requested"
    assert result.get("agent").get("machineId") == "aa:bb:cc:11:22:33"
    assert result.get("containerId") == "123456789012"
    assert result.get("agent").get("hostName") == "ip-1-2-3-4.us-west-1.compute.internal"


def test_get_capture_file(mock_response):
    # Successful response
    mock_response.return_value.content = b"abc"  # Dummy bytes
    mock_response.return_value.status_code = HTTPStatus.OK
    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_capture_file_command(client, {"capture_id": "1234567890"})
    result = result.to_context().get("Contents")


def test_get_action_execution(mock_response):
    # Successful response
    mock_response.return_value.json.return_value = GET_ACTION_EXECUTION_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_action_execution_command(client, {"action_execution_id": "b137bf86-399f-43f7-8d2b-53060af4da9f"})
    result = result.to_context().get("Contents")

    assert result.get("actionType") == "KILL_CONTAINER"
    assert result.get("status") == "COMPLETED"
    assert result.get("callerId") == "test_kill_container_07"
    assert result.get("executionContext").get("container.id") == "123456789012"


# --- _cache_is_valid ---


def test_cache_is_valid_returns_false_when_none():
    assert _cache_is_valid(None) is False


def test_cache_is_valid_returns_false_when_missing_cached_at():
    assert _cache_is_valid({"data": {}}) is False


def test_cache_is_valid_returns_true_when_fresh():
    entry = {"cached_at": time.time() - 100, "data": {}}
    assert _cache_is_valid(entry, ttl=3600) is True


def test_cache_is_valid_returns_false_when_expired():
    entry = {"cached_at": time.time() - 7200, "data": {}}
    assert _cache_is_valid(entry, ttl=3600) is False


# --- get_agent_by_mac_command ---


def test_get_agent_by_mac_api_hit(mock_response, mocker):
    mock_response.return_value.json.return_value = GET_AGENTS_CONNECTED_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    mocker.patch.object(demisto, "getIntegrationContext", return_value={})
    mock_set_ctx = mocker.patch.object(demisto, "setIntegrationContext")

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_agent_by_mac_command(client, {"machine_id": "aa:bb:cc:dd:ee:01"})
    outputs = result.to_context().get("Contents")

    assert outputs["agentId"] == "99001"
    assert outputs["customerId"] == "55001"
    assert outputs["hostName"] == "ip-10-0-1-100.us-west-2.compute.internal"
    assert outputs["machineId"] == "aa:bb:cc:dd:ee:01"
    assert outputs["hostId"] == "host-uid-aaaa-0001"
    assert outputs["clusterName"] == "prod-us-west-2"
    mock_set_ctx.assert_called_once()


def test_get_agent_by_mac_cache_hit(mocker):
    cached_agent = {
        "agentId": "99001",
        "customerId": "55001",
        "hostName": "cached-host",
        "machineId": "aa:bb:cc:dd:ee:01",
        "hostId": "host-uid-cached",
        "clusterName": "cached-cluster",
    }
    ctx = {"agent_aa:bb:cc:dd:ee:01": {"data": cached_agent, "cached_at": time.time()}}
    mocker.patch.object(demisto, "getIntegrationContext", return_value=ctx)

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_agent_by_mac_command(client, {"machine_id": "aa:bb:cc:dd:ee:01"})
    outputs = result.to_context().get("Contents")

    assert outputs["hostName"] == "cached-host"
    assert outputs["clusterName"] == "cached-cluster"


def test_get_agent_by_mac_force_refresh(mock_response, mocker):
    cached_agent = {
        "agentId": "99001",
        "customerId": "55001",
        "hostName": "stale-host",
        "machineId": "aa:bb:cc:dd:ee:01",
        "hostId": "old-uid",
        "clusterName": "old-cluster",
    }
    ctx = {"agent_aa:bb:cc:dd:ee:01": {"data": cached_agent, "cached_at": time.time()}}
    mocker.patch.object(demisto, "getIntegrationContext", return_value=ctx)
    mocker.patch.object(demisto, "setIntegrationContext")
    mock_response.return_value.json.return_value = GET_AGENTS_CONNECTED_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_agent_by_mac_command(client, {"machine_id": "aa:bb:cc:dd:ee:01", "force_refresh": "true"})
    outputs = result.to_context().get("Contents")

    assert outputs["hostName"] == "ip-10-0-1-100.us-west-2.compute.internal"


def test_get_agent_by_mac_not_found(mock_response, mocker):
    mock_response.return_value.json.return_value = GET_AGENTS_CONNECTED_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    mocker.patch.object(demisto, "getIntegrationContext", return_value={})

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    with pytest.raises(ValueError, match="No connected agent found"):
        get_agent_by_mac_command(client, {"machine_id": "ff:ff:ff:ff:ff:ff"})


def test_get_agent_by_mac_missing_machine_id():
    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    with pytest.raises(ValueError, match="machine_id .* is required"):
        get_agent_by_mac_command(client, {})


# --- get_customer_info_command ---


def test_get_customer_info_api_hit(mock_response, mocker):
    mock_response.return_value.json.return_value = GET_USERS_ME_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK
    mocker.patch.object(demisto, "getIntegrationContext", return_value={})
    mock_set_ctx = mocker.patch.object(demisto, "setIntegrationContext")

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_customer_info_command(client, {})
    outputs = result.to_context().get("Contents")

    assert outputs["customerId"] == "55001"
    assert outputs["customerName"] == "Acme Corp"
    mock_set_ctx.assert_called_once()


def test_get_customer_info_cache_hit(mocker):
    ctx = {"customer_info": {"data": {"customer_id": "55001", "customer_name": "Cached Corp"}, "cached_at": time.time()}}
    mocker.patch.object(demisto, "getIntegrationContext", return_value=ctx)

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_customer_info_command(client, {})
    outputs = result.to_context().get("Contents")

    assert outputs["customerId"] == "55001"
    assert outputs["customerName"] == "Cached Corp"


def test_get_customer_info_force_refresh(mock_response, mocker):
    ctx = {"customer_info": {"data": {"customer_id": "00000", "customer_name": "Old Corp"}, "cached_at": time.time()}}
    mocker.patch.object(demisto, "getIntegrationContext", return_value=ctx)
    mocker.patch.object(demisto, "setIntegrationContext")
    mock_response.return_value.json.return_value = GET_USERS_ME_RESPONSE
    mock_response.return_value.status_code = HTTPStatus.OK

    client = Client(base_url="https://us2.app.sysdig.com", verify=False, headers={"Authorization": "Bearer token"}, proxy=False)
    result = get_customer_info_command(client, {"force_refresh": "true"})
    outputs = result.to_context().get("Contents")

    assert outputs["customerId"] == "55001"
    assert outputs["customerName"] == "Acme Corp"