Cyberint Feed

Use the Cyberint Feed integration to get indicators from the feed.

Data Enrichment & Threat Intelligence · Cyberint · Feed

Details

IDCyberint Feed
ProviderCheck Point Software Technologies
CategoryData Enrichment & Threat Intelligence
From Version6.9.0
Docker Imagedemisto/python3:3.12.13.10404775
Supported ModulesAgentix XSIAM

README

Use the Cyberint Feed integration to get indicators from the feed.

Configure Cyberint Feed on Cortex XSOAR

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

    Parameter Description Required
    Cyberint API URL Example: https://yourcompany.cyberint.io True
    API access token   True
    Fetch indicators Should be checked (true) False
    Indicator Type Which indicator types to fetch True
    Confidence Confidence about the indicator details. The value of confidence to fetch indicators from. The value between 0-100. False
    Severity Severity about the indicator details. The value of severity to fetch indicators from. The value between 0-100. False
    Tags Supports CSV values. False
    Bypass exclusion list When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. False
    Trust any certificate (not secure)   False
    Use system proxy settings   False
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

cyberint-get-indicators


Gets indicators from the feed.

Base Command

cyberint-get-indicators

Input

Argument Name Description Required
limit The maximum number of results to return. The default value is 10. Default is 10. Optional

Context Output

Path Type Description
Cyberint.indicator.ioc_value String The indicator value.
Cyberint.indicator.ioc_type String The indicator type.
Cyberint.indicator.description String The feed description.
Cyberint.indicator.detected_activity String The feed detected activity.
Cyberint.indicator.observation_date String The feed observation date.
Cyberint.indicator.severity_score String The feed severity score.
Cyberint.indicator.confidence String The feed confidence.

Command example

!cyberint-get-indicators limit=10 execution-timeout=700

Context Example

{
    "Cyberint": [
        {
            "fields": {
                "Description": "Recognized as Malicious.",
                "FirstSeenBySource": "2024-01-23T22:53:36+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as Malicious.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc1",
                "observation_date": "2024-01-23T22:53:36+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc1"
        },
        {
            "fields": {
                "Description": "Recognized as zzz.",
                "FirstSeenBySource": "2024-01-23T22:55:36+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as zzz.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc2",
                "observation_date": "2024-01-23T22:55:36+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc2"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-23T22:53:35+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc3",
                "observation_date": "2024-01-23T22:53:35+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc3"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-23T22:55:31+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc4",
                "observation_date": "2024-01-23T22:55:31+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc4"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-23T22:55:35+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc5",
                "observation_date": "2024-01-23T22:55:35+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc5"
        },
        {
            "fields": {
                "Description": "Recognized as Trojan.xxx.",
                "FirstSeenBySource": "2024-01-23T22:55:39+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as Trojan.xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc6",
                "observation_date": "2024-01-23T22:55:39+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc6"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-12T01:39:06+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc7",
                "observation_date": "2024-01-12T01:39:06+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc7"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-23T22:55:36+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc8",
                "observation_date": "2024-01-23T22:55:36+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc8"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2023-12-16T21:28:01+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 70,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc9",
                "observation_date": "2023-12-16T21:28:01+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc9"
        },
        {
            "fields": {
                "Description": "Recognized as xxx.",
                "FirstSeenBySource": "2024-01-23T22:55:35+00:00",
                "reportedby": "Cyberint",
                "trafficlightprotocol": "GREEN"
            },
            "rawJSON": {
                "confidence": 80,
                "description": "Recognized as xxx.",
                "detected_activity": "malware_payload",
                "ioc_type": "file/sha256",
                "ioc_value": "ioc10",
                "observation_date": "2024-01-23T22:55:35+00:00",
                "severity_score": 100
            },
            "service": "Cyberint",
            "type": "File",
            "value": "ioc10"
        }
    ]
}

Human Readable Output

Indicators from Cyberint Feed
Name Type Description
Detected activity String Type of detected activity.
IoC type String The indicator type.
IoC value String The indicator value.
Observation date String Observation date of detected activity.
Severity score Number Severity score of detected activity.
Confidence Number Confidence of detected activity.
Description String Description of detected activity.

cyberint-get-file-sha256


Gets File SHA256 from the feed.

Base Command

cyberint-get-file-sha256

Input

Argument Name Description Required
value File SHA256 hash Required

Context Output

Path Type Description
Cyberint.file_sha256.entity.type String The indicator type.
Cyberint.file_sha256.entity.value String The indicator value.
Cyberint.file_sha256.enrichment.first_seen String First seen.
Cyberint.file_sha256.enrichment.download_urls String Download URLs.
Cyberint.file_sha256.enrichment.filenames String Filenames.
Cyberint.file_sha256.benign String Benign.
Cyberint.file_sha256.risk.malicious_score String Malicious score.
Cyberint.file_sha256.risk.occurrences_count String Occurrences count.

Command example

!cyberint-get-file-sha256 value=6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789

Context Example

{
    "data": {
        "entity": {
            "type": "file/sha256",
            "value": "6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789"
        },
        "risk": {
            "malicious_score": 100,
            "detected_activities": [
                {
                    "type": "malware",
                    "observation_date": "2025-03-05T14:47:43.994848+00:00",
                    "description": "",
                    "confidence": 100,
                    "occurrences_count": 1
                },
                {
                    "type": "malware_payload",
                    "observation_date": "2025-02-12T21:08:13+00:00",
                    "description": "Detected in 1 source(s). Recognized as Trojan.Agent.CYZT.",
                    "confidence": 80,
                    "occurrences_count": 1
                }
            ],
            "occurrences_count": 2
        },
        "enrichment": {
            "related_entities": null,
            "filenames": [
                "rifaien2-TwxvxoHtj44icOI0.exe"
            ],
            "first_seen": "2025-02-12T21:08:13+00:00",
            "download_urls": []
        },
        "benign": false
    }
}

Human Readable Output

File SHA256 Entity
Name Type Description
Type String The indicator type.
Value String The indicator value.
Malicious score Number Malicious score.
Benign Boolean Benign.
File SHA256 Enrichment
Name Type Description
Filenames String List of filenames.
First seen String First seen.
Download URLs String List of download URLs.
File SHA256 Detected activities
Name Type Description
Type String Type of detected activity.
Observation date String Observation date of detected activity.
Description String Description of detected activity.
Confidence Number Confidence of detected activity.
Occurrences count Number Occurrences count of detected activity.

cyberint-get-domain


Gets Domain from the feed.

Base Command

cyberint-get-domain

Input

Argument Name Description Required
value Domain Required

Context Output

Path Type Description
Cyberint.domain.entity.type String The indicator type.
Cyberint.domain.entity.value String The indicator value.
Cyberint.domain.risk.malicious_score String Malicious score.
Cyberint.domain.risk.occurrences_count String Occurrences count.
Cyberint.domain.enrichment.ips String IPs.
Cyberint.domain.enrichment.whois.created_date String Created date.
Cyberint.domain.enrichment.whois.updated_date String Updated date.
Cyberint.domain.enrichment.whois.expiration_date String Expiration date.
Cyberint.domain.enrichment.whois.registrant_name String Registrant name.
Cyberint.domain.enrichment.whois.registrant_email String Registrant email.
Cyberint.domain.enrichment.whois.registrant_organization String Registrant organization.
Cyberint.domain.enrichment.whois.registrant_country String Registrant country.
Cyberint.domain.enrichment.whois.registrant_telephone String Registrant telephone.
Cyberint.domain.enrichment.whois.technical_contact_email String Technical contact email.
Cyberint.domain.enrichment.whois.technical_contact_name String Technical contact name.
Cyberint.domain.enrichment.whois.technical_contact_organization String Technical contact organization.
Cyberint.domain.enrichment.whois.registrar_name String Registrar name.
Cyberint.domain.enrichment.whois.admin_contact_name String Admin contact name.
Cyberint.domain.enrichment.whois.admin_contact_organization String Admin contact organization.
Cyberint.domain.enrichment.whois.admin_contact_email String Admin contact email.
Cyberint.domain.benign String Benign.

Command example

!cyberint-get-domain value=dummy.com

Context Example

{
    "data": {
        "entity": {
            "type": "domain",
            "value": "domain.com"
        },
        "risk": {
            "malicious_score": 80,
            "detected_activities": [
                {
                    "type": "infecting_url",
                    "observation_date": "2025-03-05T14:47:23.534044+00:00",
                    "description": "URL that may infect it’s visitors with malware.",
                    "confidence": 100,
                    "occurrences_count": 1
                },
                {
                    "type": "phishing_website",
                    "observation_date": "2024-09-16T06:26:16+00:00",
                    "description": "Detected phishing website targeting Dummy.",
                    "confidence": 20,
                    "occurrences_count": 1
                }
            ],
            "occurrences_count": 2
        },
        "enrichment": {
            "related_entities": null,
            "ips": [
                "11.197.130.221"
            ],
            "whois": {
                "registrant_name": null,
                "registrant_email": null,
                "registrant_organization": null,
                "registrant_country": "USA",
                "registrant_telephone": null,
                "technical_contact_email": null,
                "technical_contact_name": null,
                "technical_contact_organization": null,
                "registrar_name": "Registrar.com",
                "admin_contact_name": null,
                "admin_contact_organization": null,
                "admin_contact_email": null,
                "created_date": "2024-09-10T09:29:58",
                "updated_date": "2024-10-18T05:44:51",
                "expiration_date": "2025-09-10T23:59:59"
            }
        },
        "benign": false
    }
}

Human Readable Output

Domain Entity
Name Type Description
Type String The indicator type.
Value String The indicator value.
Malicious score Number Malicious score.
Occurrences count Number Occurrences count.
Benign Boolean Benign.
Domain Enrichment
Name Type Description
IPs String List of IP addresses.
Whois registrant name String Whois registrant name.
Whois registrant email String Whois registrant email.
Whois registrant organization String Whois registrant organization.
Whois registrant country String Whois registrant country.
Whois registrant telephone String Whois registrant telephone.
Whois technical contact email String Whois technical contact email.
Whois technical contact name String Whois technical contact name.
Whois technical contact organization String Whois technical contact organization.
Whois registrar name String Whois registrar name.
Whois admin contact name String Whois admin contact name.
Whois admin contact organization String Whois admin contact organization.
Whois admin contact email String Whois admin contact email.
Whois admin contact email String Whois admin contact email.
Created date String Created date.
Updated date String Updated date.
Expiration date String Expiration date.
Domain Detected activities
Name Type Description
Type String Type of detected activity.
Observation date String Observation date of detected activity.
Description String Description of detected activity.
Confidence Number Confidence of detected activity.
Occurrences count Number Occurrences count of detected activity.

cyberint-get-ipv4


Gets Domain from the feed.

Base Command

cyberint-get-ipv4

Input

Argument Name Description Required
value IPv4 Required

Context Output

Path Type Description
Cyberint.ipv4.entity.type String The indicator type.
Cyberint.ipv4.entity.value String The indicator value.
Cyberint.ipv4.risk.malicious_score String Malicious score.
Cyberint.ipv4.risk.occurrences_count String Occurrences count.
Cyberint.ipv4.enrichment.geo.country String Country.
Cyberint.ipv4.enrichment.geo.city String City.
Cyberint.ipv4.enrichment.asn.number String ASN number.
Cyberint.ipv4.enrichment.asn.organization String ASN organization.
Cyberint.ipv4.enrichment.suspicious_urls String Suspicious URLs.
Cyberint.ipv4.enrichment.suspicious_domains String Suspicious domains.
Cyberint.ipv4.benign String Benign.

Command example

!cyberint-get-ipv4 value=1.1.1.1

Context Example

{
    "data": {
        "entity": {
            "type": "ipv4",
            "value": "11.197.130.221"
        },
        "risk": {
            "malicious_score": 100,
            "detected_activities": [
                {
                    "type": "payload_delivery",
                    "observation_date": "2025-02-13T08:38:50+00:00",
                    "description": "Detected hosting malware.",
                    "confidence": 20,
                    "occurrences_count": 836
                },
                {
                    "type": "phishing_website",
                    "observation_date": "2025-03-05T10:17:32+00:00",
                    "description": "Detected phishing website targeting Dummy, ING Direct, genericcloudflare.",
                    "confidence": 20,
                    "occurrences_count": 143
                },
                {
                    "type": "cnc_server",
                    "observation_date": "2025-02-14T22:21:12.084000+00:00",
                    "description": "Detected in 21 source(s). Recognized as Quasar RAT. Detected activity linked to: Bumblebee (Malware), Cotton Sandstorm (Threat-Actor-Group), DadSec (Malware), GHOSTSPIDER (Malware), Quasar RAT (Malware), Salt Typhoon (Threat-Actor-Group), Sneaky 2FA (Malware), Vidar (Malware)",
                    "confidence": 90,
                    "occurrences_count": 21
                }
            ],
            "occurrences_count": 1000
        },
        "enrichment": {
            "related_entities": [
                {
                    "entity_id": "c654837d-444e-4f5c-a444-09fd8250696c",
                    "entity_type": "Malware",
                    "entity_name": "GHOSTSPIDER"
                },
                {
                    "entity_id": "70b54325-05ea-46c6-b4e9-b25bc3617104",
                    "entity_type": "Threat-Actor-Group",
                    "entity_name": "Salt Typhoon"
                },
                {
                    "entity_id": "baffd4c4-4483-4b84-96eb-0d19af94d2e8",
                    "entity_type": "Malware",
                    "entity_name": "DadSec"
                },
                {
                    "entity_id": "862341a5-1951-4e09-b3c1-baac41dc7bcb",
                    "entity_type": "Malware",
                    "entity_name": "Vidar"
                },
                {
                    "entity_id": "7b0a986f-733e-4497-8867-6aed00b802b8",
                    "entity_type": "Threat-Actor-Group",
                    "entity_name": "Cotton Sandstorm"
                },
                {
                    "entity_id": "58cbb47d-176d-4937-9ebc-5121ceb36cf9",
                    "entity_type": "Malware",
                    "entity_name": "Sneaky 2FA"
                },
                {
                    "entity_id": "2728ad3e-d870-4654-afd3-9a839f97dd72",
                    "entity_type": "Malware",
                    "entity_name": "Bumblebee"
                },
                {
                    "entity_id": "fc26b8a7-a7cc-47b8-be1e-92b7a969543b",
                    "entity_type": "Malware",
                    "entity_name": "Quasar RAT"
                }
            ],
            "geo": {
                "country": "United States",
                "city": null
            },
            "asn": {
                "number": 16509,
                "organization": "AMAZON-02"
            },
            "suspicious_urls": [],
            "suspicious_domains": []
        },
        "benign": false
    }
}

Human Readable Output

IPv4 Entity
Name Type Description
Type String The indicator type.
Value String The indicator value.
Malicious score Number Malicious score.
Occurrences count Number Occurrences count.
IPs String List of IP addresses.
Hostname String Hostname.
Domain String Domain.
Benign Boolean Benign.
IPv4 Enrichment
Name Type Description
Suspicious Urls String List of Suspicious Urls.
Suspicious Domains String List of Suspicious domains.
IPv4 Detected activities
Name Type Description
Type String Type of detected activity.
Observation date String Observation date of detected activity.
Description String Description of detected activity.
Confidence Number Confidence of detected activity.
Occurrences count Number Occurrences count of detected activity.

cyberint-get-url


Gets Domain from the feed.

Base Command

cyberint-get-url

Input

Argument Name Description Required
value URL Required

Context Output

Path Type Description
Cyberint.url.entity.type String The indicator type.
Cyberint.url.entity.value String The indicator value.
Cyberint.url.risk.malicious_score String Malicious score.
Cyberint.url.risk.occurrences_count String Occurrences count.
Cyberint.url.enrichment.ips String IPs.
Cyberint.url.enrichment.hostname String Hostname.
Cyberint.url.enrichment.domain String Domain.
Cyberint.url.benign String Benign.

Command example

!cyberint-get-url value=http://dummy.com

Context Example

{
    "data": {
        "entity": {
            "type": "url",
            "value": "http://dummy.com"
        },
        "risk": {
            "malicious_score": 80,
            "detected_activities": [
                {
                    "type": "infecting_url",
                    "observation_date": "2025-03-05T11:18:01.941280+00:00",
                    "description": "URL that may infect it’s visitors with malware.",
                    "confidence": 100,
                    "occurrences_count": 1
                }
            ],
            "occurrences_count": 1
        },
        "enrichment": {
            "related_entities": null,
            "ips": [],
            "hostname": null,
            "domain": null
        },
        "benign": false
    }
}

Human Readable Output

URL Entity
Name Type Description
Type String The indicator type.
Value String The indicator value.
Malicious score Number Malicious score.
Occurrences count Number Occurrences count.
IPs String List of IP addresses.
Hostname String Hostname.
Domain String Domain.
Benign Boolean Benign.
URL Detected activities
Name Type Description
Type String Type of detected activity.
Observation date String Observation date of detected activity.
Description String Description of detected activity.
Confidence Number Confidence of detected activity.
Occurrences count Number Occurrences count of detected activity.

cyberint-cve-enrich


Enriches one or more CVE identifiers with Cyberint vulnerability intelligence, including EPSS, CVSS, CWE, the Cyberint CVE score and active-exploitation evidence.

Base Command

cyberint-cve-enrich

Input

Argument Name Description Required
cve_id CVE identifier to enrich (e.g., CVE-2024-1234). Supports a comma-separated list of identifiers. Required

Context Output

Path Type Description
Cyberint.CVE.cve_id String The CVE identifier.
Cyberint.CVE.cyberint_score Number Cyberint proprietary risk score for the CVE.
Cyberint.CVE.epss Number EPSS (Exploit Prediction Scoring System) probability score.
Cyberint.CVE.description String The CVE description.
Cyberint.CVE.published Date The date the CVE was published.
Cyberint.CVE.last_updated Date The date the CVE was last updated.
Cyberint.CVE.cvss.cvss_v3.base_score Number CVSS v3 base score.
Cyberint.CVE.cvss.cvss_v3.base_severity String CVSS v3 base severity.
Cyberint.CVE.cvss.cvss_v4.base_score Number CVSS v4 base score.
Cyberint.CVE.cwes.cwe_id String Associated CWE identifier.
Cyberint.CVE.cwes.name String Associated CWE name.
Cyberint.CVE.risk_factors Unknown Risk factors associated with the CVE.
Cyberint.CVE.exploited_by Unknown Threat actors observed exploiting the CVE.
Cyberint.CVE.products Unknown Products affected by the CVE.
Cyberint.CVE.recommendation String Cyberint remediation recommendation for the CVE.
Cyberint.CVE.references Unknown External references for the CVE.
CVE.ID String The CVE identifier.
CVE.CVSS String The CVSS score of the CVE.
CVE.Published Date The date the CVE was published.
CVE.Description String The CVE description.

Command Example

!cyberint-cve-enrich cve_id="CVE-2024-1234"

cyberint-credential-leak-lookup


Looks up leaked employee or customer credentials associated with a company domain, using the Cyberint leaked-credentials API.

Base Command

cyberint-credential-leak-lookup

Input

Argument Name Description Required
domain The company domain to search leaked credentials for (e.g., example.com). Required
last_seen_from Only return credentials last seen on or after this ISO-formatted date (YYYY-MM-DDTHH:MM:SSZ). Optional
last_seen_to Only return credentials last seen on or before this ISO-formatted date (YYYY-MM-DDTHH:MM:SSZ). Optional
limit Maximum number of leaked credential records to return per credential type (employee/customer). Optional

Context Output

Path Type Description
Cyberint.LeakedCredential.domain String The company domain that was searched.
Cyberint.LeakedCredential.total Number Total number of leaked credential records returned.
Cyberint.LeakedCredential.employee_total Number Number of leaked employee credential records returned.
Cyberint.LeakedCredential.customer_total Number Number of leaked customer credential records returned.
Cyberint.LeakedCredential.employee_credentials.username String The leaked employee account username.
Cyberint.LeakedCredential.employee_credentials.first_seen Date The date the employee credential was first observed.
Cyberint.LeakedCredential.employee_credentials.last_seen Date The date the employee credential was last observed.
Cyberint.LeakedCredential.employee_credentials.entries Unknown The leaked credential entries (password, source, URL) for the employee account.
Cyberint.LeakedCredential.customer_credentials.username String The leaked customer account username.
Cyberint.LeakedCredential.customer_credentials.first_seen Date The date the customer credential was first observed.
Cyberint.LeakedCredential.customer_credentials.last_seen Date The date the customer credential was last observed.
Cyberint.LeakedCredential.customer_credentials.entries Unknown The leaked credential entries (password, source, URL) for the customer account.

Command Example

!cyberint-credential-leak-lookup domain="example.com"

Configuration parameters

  • url — Cyberint API URL (required)
  • client_name — Company Name (required)
  • access_token — API Key (Leave empty. Fill in the API Key in the password field.) (required)
  • feed — Fetch indicators
  • feedReputation — Indicator Reputation
  • feedReliability — Source Reliability (required)
  • tlp_color — Traffic Light Protocol Color
  • indicator_type — Indicator Type (required)
  • feed_name — Feed Name (required)
  • confidence_from — Confidence
  • severity_from — Severity
  • feedExpirationPolicy
  • feedExpirationInterval
  • feedFetchInterval — Feed Fetch Interval
  • feedTags — Tags
  • feedBypassExclusionList — Bypass exclusion list
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (7)

  • cyberint-credential-leak-lookup

    Looks up leaked employee or customer credentials associated with a company domain, using the Cyberint leaked-credentials API.

  • cyberint-cve-enrich

    Enriches one or more CVE identifiers with Cyberint vulnerability intelligence, including EPSS, CVSS, CWE, the Cyberint CVE score and active-exploitation evidence.

  • cyberint-get-domain

    Gets Domain enrichment from the feed.

  • cyberint-get-file-sha256

    Gets File SHA256 enrichment from the feed.

  • cyberint-get-indicators

    Gets indicators from the feed.

  • cyberint-get-ipv4

    Gets IPv4 enrichment from the feed.

  • cyberint-get-url

    Gets URL enrichment from the feed.

from datetime import datetime, timedelta
import re
from unittest import mock
from unittest.mock import MagicMock, patch

import FeedCyberint
import pytest
from CommonServerPython import DemistoException

date_time = "2025-01-01"

BASE_URL = "https://feed-example.com"
REQUEST_URL1 = f"{BASE_URL}/ioc/api/v1/feed/daily/{date_time}?limit=1000&offset=0"
REQUEST_URL2 = f"{BASE_URL}/ioc/api/v1/feed/daily/{date_time}?limit=1000&offset=1000"
REQUEST_URL3 = f"{BASE_URL}/ioc/api/v1/feed/daily/{date_time}?limit=20000&offset=0"
REQUEST_URL4 = f"{BASE_URL}/ioc/api/v1/feed/daily/{date_time}?limit=20000&offset=20000"
REQUEST_URL5 = f"{BASE_URL}/ioc/api/v1/feed/daily/{date_time}?limit=20&offset=0"
REQUEST_URL6 = f"{BASE_URL}/ioc/api/v1/url?value=http://dummy.com"
REQUEST_URL7 = f"{BASE_URL}/ioc/api/v1/ipv4?value=1.1.1.1"
REQUEST_URL8 = f"{BASE_URL}/ioc/api/v1/domain?value=dummy.com"
REQUEST_URL9 = f"{BASE_URL}/ioc/api/v1/v1/file/sha256?value=6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789"
TOKEN = "example_token"


def load_mock_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/indicators.jsonb") as file:
        return file.read()


def load_mock_empty_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/empty.jsonb") as file:
        return file.read()


def load_mock_url_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/url.json") as file:
        return file.read()


def load_mock_ipv4_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/ipv4.json") as file:
        return file.read()


def load_mock_file_sha256_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/file_sha256.json") as file:
        return file.read()


def load_mock_domain_response() -> str:
    """Load mock file that simulates an API response.

    Returns:
        str: Mock file content.
    """
    with open("test_data/domain.json") as file:
        return file.read()


@pytest.fixture()
def mock_client() -> FeedCyberint.Client:
    """
    Establish a mock connection to the client with access token.

    Returns:
        Client: Mock connection to client.
    """
    return FeedCyberint.Client(
        base_url=BASE_URL,
        access_token=TOKEN,
        verify=False,
        proxy=False,
    )


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_build_iterator(
    is_execution_time_exceeded_mock,
    requests_mock,
    mock_client: FeedCyberint.Client,
):
    """
    Scenario:
    - Test retrieving a list IOCs from Cyberint feed.

    Given:
    - mock_client.

    When:
    - Called the build_iterator request (this request called by all integration commands).

    Then:
    - Ensure that the IP values is correct.
    - Ensure that the URL values is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL1, text=response1)
    requests_mock.get(REQUEST_URL2, text=response2)

    with (
        patch.object(mock_client, "request_daily_feed", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "IP" if x == "1.1.1.1" else None
        indicators = mock_client.request_daily_feed()

        assert indicators is not None


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_get_indicators_command(
    is_execution_time_exceeded_mock,
    mock_client,
    requests_mock,
):
    """
    Scenario:
    - Test retrieving indicators by filters from feed.

    Given:
    - mock_client.

    When:
    - Called the get_indicators_command.

    Then:
    - Ensure that the IP values is correct.
    - Ensure that the URL values is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL5, text=response1)
    requests_mock.get(REQUEST_URL6, text=response2)

    args = {"date": date_time, "limit": 20, "offset": 0}

    with (
        patch.object(FeedCyberint, "get_indicators_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "IP" if x == "1.1.1.1" else None
        result = FeedCyberint.get_indicators_command(mock_client, args)

        assert result == response1


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_get_url_command(
    is_execution_time_exceeded_mock,
    mock_client,
    requests_mock,
):
    """
    Scenario:
    - Test retrieving URL information from feed.

    Given:
    - mock_client.

    When:
    - Called the get_url_command.

    Then:
    - Ensure that the response is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_url_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL6, text=response1)
    requests_mock.get(REQUEST_URL6, text=response2)

    args = {"value": "http://dummy.com"}

    with (
        patch.object(FeedCyberint, "get_url_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "URL" if x == "http://dummy.com" else None
        result = FeedCyberint.get_url_command(mock_client, args)

        assert result == response1


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_get_ipv4_command(
    is_execution_time_exceeded_mock,
    mock_client,
    requests_mock,
):
    """
    Scenario:
    - Test retrieving IPv4 information from feed.

    Given:
    - mock_client.

    When:
    - Called the get_ipv4_command.

    Then:
    - Ensure that the response is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_ipv4_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL7, text=response1)
    requests_mock.get(REQUEST_URL7, text=response2)

    args = {"value": "1.1.1.1"}

    with (
        patch.object(FeedCyberint, "get_ipv4_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "IP" if x == "1.1.1.1" else None
        result = FeedCyberint.get_ipv4_command(mock_client, args)

        assert result == response1


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_get_domain_command(
    is_execution_time_exceeded_mock,
    mock_client,
    requests_mock,
):
    """
    Scenario:
    - Test retrieving Domain information from feed.

    Given:
    - mock_client.

    When:
    - Called the get_domain_command.

    Then:
    - Ensure that the response is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_domain_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL8, text=response1)
    requests_mock.get(REQUEST_URL8, text=response2)

    args = {"value": "dummy.com"}

    with (
        patch.object(FeedCyberint, "get_domain_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "Domain" if x == "dummy.com" else None
        result = FeedCyberint.get_domain_command(mock_client, args)

        assert result == response1


@patch("FeedCyberint.tableToMarkdown")
def test_get_domain_command_with_invalid_arg(mock_client):
    """Test get_domain_command when value argument is invalid."""

    err_msg = (
        "1 validation error for Request\nquery -> value\n  string does not match regex "
        '"^(?:(?:(?:[[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?)\\.))*(?:[a-z0-9][a-z0-9\\-]*[a-z0-9])$" '
        "(type=value_error.str.regex; pattern="
        "^(?:(?:(?:[[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?)\\.))*(?:[a-z0-9][a-z0-9\\-]*[a-z0-9])$)"
    )

    # Mock args input with invalid value
    args = {"value": "@"}

    mock_client.retrieve_domain_from_api.side_effect = TypeError(err_msg)

    # Call the function
    with pytest.raises(TypeError, match=re.escape(err_msg)):
        FeedCyberint.get_domain_command(mock_client, args)


@patch("FeedCyberint.tableToMarkdown")
def test_get_url_command_with_invalid_arg(mock_client):
    """Test get_url_command when value argument is invalid."""

    err_msg = "1 validation error for Request\nquery -> value\n  invalid or missing URL scheme (type=value_error.url.scheme)"

    # Mock args input with invalid value
    args = {"value": "@"}

    mock_client.retrieve_url_from_api.side_effect = TypeError(err_msg)

    # Call the function
    with pytest.raises(TypeError, match=re.escape(err_msg)):
        FeedCyberint.get_url_command(mock_client, args)


@patch("FeedCyberint.tableToMarkdown")
def test_get_ipv4_command_with_invalid_arg(mock_client):
    """Test get_ipv4_command when value argument is invalid."""

    err_msg = "1 validation error for Request\nquery -> value\n  value is not a valid IPv4 address (type=value_error.ipv4address)"

    # Mock args input with invalid value
    args = {"value": "@"}

    mock_client.retrieve_ipv4_from_api.side_effect = TypeError(err_msg)

    # Call the function
    with pytest.raises(TypeError, match=re.escape(err_msg)):
        FeedCyberint.get_ipv4_command(mock_client, args)


@patch("FeedCyberint.tableToMarkdown")
def test_get_file_sha256_command_with_invalid_arg(mock_client):
    """Test get_file_sha256_command when value argument is invalid."""

    err_msg = (
        "1 validation error for Request\nquery -> value\n  string does not match regex "
        '"^[a-f0-9]{64}$" (type=value_error.str.regex; pattern=^[a-f0-9]{64}$)'
    )

    # Mock args input with invalid value
    args = {"value": "@"}

    mock_client.retrieve_file_sha256_from_api.side_effect = TypeError(err_msg)

    # Call the function
    with pytest.raises(TypeError, match=re.escape(err_msg)):
        FeedCyberint.get_file_sha256_command(mock_client, args)


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_get_file_sha256_command(
    is_execution_time_exceeded_mock,
    mock_client,
    requests_mock,
):
    """
    Scenario:
    - Test retrieving File SHA256 information from feed.

    Given:
    - mock_client.

    When:
    - Called the get_file_sha256_command.

    Then:
    - Ensure that the response is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_file_sha256_response()
    response2 = load_mock_file_sha256_response()

    requests_mock.get(REQUEST_URL8, text=response1)
    requests_mock.get(REQUEST_URL8, text=response2)

    args = {"value": "6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789"}

    with (
        patch.object(FeedCyberint, "get_file_sha256_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = (
            lambda x: "File" if x == "6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789" else None
        )
        result = FeedCyberint.get_file_sha256_command(mock_client, args)

        assert result == response1


@mock.patch("FeedCyberint.is_execution_time_exceeded")
def test_fetch_indicators_command_ok(is_execution_time_exceeded_mock, mock_client: FeedCyberint.Client, requests_mock):
    """
    Scenario:
    - Test retrieving indicators by filters from feed.

    Given:
    - mock_client.

    When:
    - Called the fetch_indicators_command.

    Then:
    - Ensure that the IP values is correct.
    - Ensure that the URL values is correct.
    """
    is_execution_time_exceeded_mock.return_value = False

    response1 = load_mock_response()
    response2 = load_mock_empty_response()

    requests_mock.get(REQUEST_URL3, text=response1)
    requests_mock.get(REQUEST_URL4, text=response2)

    with (
        patch.object(FeedCyberint, "fetch_indicators_command", return_value=response1),
        patch("CommonServerPython.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "IP" if x == "1.1.1.1" else None
        result = FeedCyberint.fetch_indicators_command(mock_client, {})  # pass empty params dict

        assert result is not None


def test_ioc_header_transformer():
    """
    Test the ioc_header_transformer function to ensure it correctly transforms headers.
    """
    # Test predefined headers
    assert FeedCyberint.ioc_header_transformer("detected_activity") == "Detected activity"
    assert FeedCyberint.ioc_header_transformer("ioc_type") == "IoC type"
    assert FeedCyberint.ioc_header_transformer("ioc_value") == "IoC value"
    assert FeedCyberint.ioc_header_transformer("observation_date") == "Observation date"
    assert FeedCyberint.ioc_header_transformer("severity_score") == "Severity score"
    assert FeedCyberint.ioc_header_transformer("confidence") == "Confidence"
    assert FeedCyberint.ioc_header_transformer("description") == "Description"

    # Test fallback case with a mock
    with patch("FeedCyberint.string_to_table_header") as mock_string_to_table_header:
        mock_string_to_table_header.return_value = "Fallback Header"
        result = FeedCyberint.ioc_header_transformer("custom_header")
        mock_string_to_table_header.assert_called_once_with("custom_header")
        assert result == "Fallback Header"


def test_indicator_header_transformer():
    """
    Test the indicator_header_transformer function to ensure it correctly transforms headers.
    """
    # Test predefined headers
    assert FeedCyberint.indicator_header_transformer("type") == "Type"
    assert FeedCyberint.indicator_header_transformer("value") == "Value"
    assert FeedCyberint.indicator_header_transformer("malicious_score") == "Malicious score"
    assert FeedCyberint.indicator_header_transformer("detected_activities") == "Detected activities"
    assert FeedCyberint.indicator_header_transformer("related_entities") == "Related entities"
    assert FeedCyberint.indicator_header_transformer("filenames") == "Filenames"
    assert FeedCyberint.indicator_header_transformer("first_seen") == "First seen"
    assert FeedCyberint.indicator_header_transformer("download_urls") == "Download URLs"
    assert FeedCyberint.indicator_header_transformer("benign") == "Benign"
    assert FeedCyberint.indicator_header_transformer("observation_date") == "Observation date"
    assert FeedCyberint.indicator_header_transformer("occurrences_count") == "Occurrences count"
    assert FeedCyberint.indicator_header_transformer("ips") == "IPs"
    assert FeedCyberint.indicator_header_transformer("registrant_name") == "Whois registrant name"
    assert FeedCyberint.indicator_header_transformer("registrant_email") == "Whois registrant email"
    assert FeedCyberint.indicator_header_transformer("registrant_organization") == "Whois registrant organization"
    assert FeedCyberint.indicator_header_transformer("registrant_country") == "Whois registrant country"
    assert FeedCyberint.indicator_header_transformer("registrant_telephone") == "Whois registrant telephone"
    assert FeedCyberint.indicator_header_transformer("technical_contact_email") == "Whois technical contact email"
    assert FeedCyberint.indicator_header_transformer("technical_contact_name") == "Whois technical contact name"
    assert FeedCyberint.indicator_header_transformer("technical_contact_organization") == "Whois technical contact organization"
    assert FeedCyberint.indicator_header_transformer("registrar_name") == "Whois registrar name"
    assert FeedCyberint.indicator_header_transformer("admin_contact_name") == "Whois admin contact name"
    assert FeedCyberint.indicator_header_transformer("admin_contact_organization") == "Whois admin contact organization"
    assert FeedCyberint.indicator_header_transformer("admin_contact_email") == "Whois admin contact email"
    assert FeedCyberint.indicator_header_transformer("created_date") == "Created date"
    assert FeedCyberint.indicator_header_transformer("updated_date") == "Updated date"
    assert FeedCyberint.indicator_header_transformer("expiration_date") == "Expiration date"
    assert FeedCyberint.indicator_header_transformer("hostname") == "Hostname"
    assert FeedCyberint.indicator_header_transformer("domain") == "Domain"
    assert FeedCyberint.indicator_header_transformer("asn_number") == "ASN number"
    assert FeedCyberint.indicator_header_transformer("asn_organization") == "ASN organization"

    # Test fallback case with a mock
    with patch("FeedCyberint.string_to_table_header") as mock_string_to_table_header:
        mock_string_to_table_header.return_value = "Fallback Header"
        result = FeedCyberint.indicator_header_transformer("custom_header")
        mock_string_to_table_header.assert_called_once_with("custom_header")
        assert result == "Fallback Header"


def test_is_execution_time_exceeded_within_limit():
    """
    Test is_execution_time_exceeded when execution time is within the timeout limit.
    """
    # Use now to create a naive datetime consistent with implementation (datetime.now())
    start_time = datetime.now() - timedelta(seconds=5)  # Well within 20 minute (1200s) timeout
    result = FeedCyberint.is_execution_time_exceeded(start_time)
    assert result is False, "Execution time is within the limit but returned True."


def test_is_execution_time_exceeded_exceeded_limit():
    """
    Test is_execution_time_exceeded when execution time exceeds the timeout limit.
    """
    start_time = datetime.now() - timedelta(seconds=FeedCyberint.EXECUTION_TIMEOUT_SECONDS + 10)  # Exceeds timeout
    result = FeedCyberint.is_execution_time_exceeded(start_time)
    assert result is True, "Execution time exceeded the limit but returned False."


@patch("FeedCyberint.datetime")
def test_is_execution_time_exceeded_mocked(mock_datetime):
    """
    Test is_execution_time_exceeded with mocked datetime to simulate precise timing.
    Patch now() instead of utcnow() to match implementation.
    """
    # Preserve ability to construct new datetime objects
    mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)
    base_time = datetime(2024, 1, 1, 12, 0, 0)

    # Simulate time just over the limit
    mock_datetime.now.return_value = base_time + timedelta(seconds=FeedCyberint.EXECUTION_TIMEOUT_SECONDS + 1)
    result = FeedCyberint.is_execution_time_exceeded(base_time)
    assert result is True, "Execution time exceeded the limit but returned False."

    # Simulate time well within the limit
    mock_datetime.now.return_value = base_time + timedelta(seconds=5)
    result = FeedCyberint.is_execution_time_exceeded(base_time)
    assert result is False, "Execution time is within the limit but returned True."


def test_get_yesterday_time():
    """
    Test the get_yesterday_time function to ensure it returns the correct date for yesterday.
    """
    # Define a mock current time
    mock_now = datetime(2024, 12, 27, 15, 0, 0)  # Example fixed time

    # Patch datetime.now to return the mock_now
    with patch("FeedCyberint.datetime") as mock_datetime:
        mock_datetime.now.return_value = mock_now
        mock_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw)

        # Call the function
        result = FeedCyberint.get_yesterday_time()

    # Expected result
    expected_yesterday = (mock_now - timedelta(days=1)).strftime(FeedCyberint.DATE_FORMAT)

    # Assert the result matches the expected value
    assert result == expected_yesterday, f"Expected {expected_yesterday}, got {result}"


@patch("FeedCyberint.datetime")
def test_is_x_minutes_ago_yesterday_true(mock_datetime):
    """Test when x minutes ago falls on yesterday."""
    # Mock current datetime to Jan 2, 2025, 00:05 AM
    mock_datetime.now.return_value = datetime(2025, 1, 2, 0, 5)
    mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)

    assert FeedCyberint.is_x_minutes_ago_yesterday(10) is True


@patch("FeedCyberint.datetime")
def test_is_x_minutes_ago_yesterday_false_same_day(mock_datetime):
    """Test when x minutes ago is still today."""
    # Mock current datetime to Jan 2, 2025, 12:10 AM
    mock_datetime.now.return_value = datetime(2025, 1, 2, 12, 10)
    mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)

    assert FeedCyberint.is_x_minutes_ago_yesterday(5) is False


@patch("FeedCyberint.datetime")
def test_is_x_minutes_ago_yesterday_false_future(mock_datetime):
    """Test edge case when x minutes ago would result in a future date."""
    # Mock current datetime to Jan 1, 2025, 11:59 PM
    mock_datetime.now.return_value = datetime(2025, 1, 1, 23, 59)
    mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)

    assert FeedCyberint.is_x_minutes_ago_yesterday(-5) is False


@patch("FeedCyberint.datetime")
def test_is_x_minutes_ago_yesterday_edge_case(mock_datetime):
    """Test edge case when x minutes ago is exactly the last second of yesterday."""
    # Mock current datetime to Jan 2, 2025, 00:00 AM
    mock_datetime.now.return_value = datetime(2025, 1, 2, 0, 0)
    mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs)

    assert FeedCyberint.is_x_minutes_ago_yesterday(1) is True


def test_retrieve_indicators_from_api_success(mock_client, requests_mock):
    """Test retrieve_indicators_from_api with a successful response."""

    date_time = "2025-01-01"
    limit = 100
    offset = 0
    mock_response = '{"ioc_value": "example.com"}\n{"ioc_value": "malicious.com"}'

    # Mock the HTTP request
    url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
    requests_mock.get(
        f"{BASE_URL}{url_suffix}",
        text=mock_response,
        status_code=200,
    )

    response = FeedCyberint.Client.retrieve_indicators_from_api(mock_client, date_time, limit, offset)

    assert response == mock_response


def test_retrieve_indicators_from_api_failure(mock_client, requests_mock):
    """Test retrieve_indicators_from_api with an HTTP error."""

    date_time = "2025-01-01"
    limit = 100
    offset = 0

    # Mock the HTTP request to return a 500 error
    url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
    requests_mock.get(
        f"{BASE_URL}{url_suffix}",
        status_code=500,
        text="Internal Server Error",
    )

    with pytest.raises(DemistoException):
        FeedCyberint.Client.retrieve_indicators_from_api(mock_client, date_time, limit, offset)


def test_retrieve_indicators_from_api_timeout(mock_client, requests_mock):
    """Test retrieve_indicators_from_api with a timeout."""

    date_time = "2025-01-01"
    limit = 100
    offset = 0

    # Mock the HTTP request to simulate a timeout
    url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
    requests_mock.get(
        f"{BASE_URL}{url_suffix}",
        exc=TimeoutError("Request timed out"),
    )

    with pytest.raises(TimeoutError):
        FeedCyberint.Client.retrieve_indicators_from_api(mock_client, date_time, limit, offset)


def test_retrieve_indicators_from_api_invalid_response(mock_client, requests_mock):
    """Test retrieve_indicators_from_api with an invalid response."""

    date_time = "2025-01-01"
    limit = 100
    offset = 0
    mock_response = "Invalid JSON response"

    # Mock the HTTP request
    url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
    requests_mock.get(
        f"{BASE_URL}{url_suffix}",
        text=mock_response,
        status_code=200,
    )

    response = FeedCyberint.Client.retrieve_indicators_from_api(mock_client, date_time, limit, offset)

    assert response == mock_response


def test_test_module_forbidden_error(mock_client):
    """Test test_module with a forbidden error."""
    # Mock `request_daily_feed` to raise a DemistoException with FORBIDDEN status
    exception = DemistoException("Forbidden")
    exception.res = MagicMock(status_code=403)
    mock_client.request_daily_feed = MagicMock(side_effect=exception)

    result = FeedCyberint.test_module(mock_client, feed_enabled=True)

    assert result == "Authorization Error: invalid `API Token`"
    mock_client.request_daily_feed.assert_called_once_with(limit=10, test=True)


def test_test_module_unexpected_error(mock_client):
    """Test test_module with an unexpected error."""
    # Mock `request_daily_feed` to raise a generic DemistoException
    exception = DemistoException("Unexpected error")
    FeedCyberint.Client.request_daily_feed = MagicMock(side_effect=exception)

    with pytest.raises(DemistoException, match="Unexpected error"):
        FeedCyberint.test_module(mock_client, feed_enabled=True)

    FeedCyberint.Client.request_daily_feed.assert_called_once_with(limit=10, test=True)


@patch("FeedCyberint.datetime")
def test_get_today_time(mock_datetime):
    """Test get_today_time to ensure it returns the correct formatted date."""
    # Define a fixed datetime for testing
    fixed_datetime = datetime(2025, 1, 2, 12, 30, 45)
    mock_datetime.now.return_value = fixed_datetime

    # Call the function
    result = FeedCyberint.get_today_time()

    # Assert the result matches the expected formatted string
    assert result == fixed_datetime.strftime(FeedCyberint.DATE_FORMAT)
    mock_datetime.now.assert_called_once()


@patch("FeedCyberint.fetch_indicators")
@patch("FeedCyberint.get_yesterday_time")
@patch("FeedCyberint.is_x_minutes_ago_yesterday")
def test_fetch_indicators_command(mock_is_x_minutes_ago_yesterday, mock_get_yesterday_time, mock_fetch_indicators, mock_client):
    """Test fetch_indicators_command function."""
    # Mock parameters
    params = {
        "tlp_color": "RED",
        "feedTags": "tag1,tag2",
        "severity_from": "2",
        "confidence_from": "50",
        "feed_name": "feed1,feed2",
        "indicator_type": "IP,Domain",
        "feedFetchInterval": "1440",
    }

    # Mock return values for the helper functions
    mock_is_x_minutes_ago_yesterday.return_value = True
    mock_get_yesterday_time.return_value = "2024-12-31T00:00:00Z"
    mock_fetch_indicators.side_effect = [
        [{"indicator": "192.168.1.1", "type": "IP"}],
        [{"indicator": "example.com", "type": "Domain"}],
    ]

    # Call the function
    result = FeedCyberint.fetch_indicators_command(mock_client, params)

    # Assertions
    assert len(result) == 2
    assert result[0]["indicator"] == "192.168.1.1"
    assert result[0]["type"] == "IP"
    assert result[1]["indicator"] == "example.com"
    assert result[1]["type"] == "Domain"

    # Verify helper function calls
    mock_is_x_minutes_ago_yesterday.assert_called_once_with(1440)
    mock_get_yesterday_time.assert_called_once()
    assert mock_fetch_indicators.call_count == 2
    mock_fetch_indicators.assert_any_call(
        client=mock_client,
        date_time="2024-12-31T00:00:00Z",
        tlp_color="RED",
        feed_tags=["tag1", "tag2"],
        feed_names=["feed1", "feed2"],
        indicator_types=["IP", "Domain"],
        severity_from=2,
        confidence_from=50,
    )
    mock_fetch_indicators.assert_any_call(
        client=mock_client,
        tlp_color="RED",
        feed_tags=["tag1", "tag2"],
        feed_names=["feed1", "feed2"],
        indicator_types=["IP", "Domain"],
        severity_from=2,
        confidence_from=50,
    )


@patch("FeedCyberint.demisto")
def test_main_test_module(mock_demisto, mock_client):
    """Test main() with the 'test-module' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "test-module"

    # Mock test_module
    mock_test_module = MagicMock()
    mock_test_module.return_value = "ok"

    # Replace test_module with mock
    with patch("FeedCyberint.test_module", mock_test_module):
        FeedCyberint.main()

    # Assertions
    mock_test_module.assert_called_once()


@patch("FeedCyberint.demisto")
def test_main_get_indicators(mock_demisto, mock_client):
    """Test main() with the 'cyberint-get-indicators' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "cyberint-get-indicators"
    mock_demisto.args.return_value = {"arg1": "value1"}

    # Mock get_indicators_command
    mock_get_indicators_command = MagicMock()
    mock_get_indicators_command.return_value = "indicator-results"

    # Replace get_indicators_command with mock
    with patch("FeedCyberint.get_indicators_command", mock_get_indicators_command):
        FeedCyberint.main()


@patch("FeedCyberint.demisto")
def test_main_get_url(mock_demisto, mock_client):
    """Test main() with the 'cyberint-get-url' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "cyberint-get-url"
    mock_demisto.args.return_value = {"arg1": "http://dummy.com"}

    # Mock get_url_command
    mock_get_url_command = MagicMock()
    mock_get_url_command.return_value = "url-results"

    # Replace get_url_command with mock
    with patch("FeedCyberint.get_url_command", mock_get_url_command):
        FeedCyberint.main()


@patch("FeedCyberint.demisto")
def test_main_get_domain(mock_demisto, mock_client):
    """Test main() with the 'cyberint-get-domain' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "cyberint-get-domain"
    mock_demisto.args.return_value = {"arg1": "dummy.com"}

    # Mock get_domain_command
    mock_get_domain_command = MagicMock()
    mock_get_domain_command.return_value = "domain-results"

    # Replace get_domain_command with mock
    with patch("FeedCyberint.get_domain_command", mock_get_domain_command):
        FeedCyberint.main()


@patch("FeedCyberint.demisto")
def test_main_get_ipv4(mock_demisto, mock_client):
    """Test main() with the 'cyberint-get-ipv4' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "cyberint-get-ipv4"
    mock_demisto.args.return_value = {"arg1": "1.1.1.1"}

    # Mock get_ipv4_command
    mock_get_ipv4_command = MagicMock()
    mock_get_ipv4_command.return_value = "ipv4-results"

    # Replace get_ipv4_command with mock
    with patch("FeedCyberint.get_ipv4_command", mock_get_ipv4_command):
        FeedCyberint.main()


@patch("FeedCyberint.demisto")
def test_main_get_file_sha256(mock_demisto, mock_client):
    """Test main() with the 'cyberint-get-file-sha256' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "cyberint-get-file-sha256"
    mock_demisto.args.return_value = {"arg1": "6a7b02c43837dcb8e40d271edb88d13d2e723c721a74931857aaef4853317789"}

    # Mock get_file_sha256_command
    mock_get_file_sha256_command = MagicMock()
    mock_get_file_sha256_command.return_value = "file-sha256-results"

    # Replace get_file_sha256_command with mock
    with patch("FeedCyberint.get_file_sha256_command", mock_get_file_sha256_command):
        FeedCyberint.main()


@patch("FeedCyberint.demisto")
def test_main_fetch_indicators(mock_demisto, mock_client):
    """Test main() with the 'fetch-indicators' command."""
    # Mock parameters and command
    mock_demisto.params.return_value = {
        "url": "https://example.com",
        "access_token": {"password": "test-token"},
        "insecure": False,
        "proxy": False,
    }
    mock_demisto.command.return_value = "fetch-indicators"

    # Mock fetch_indicators_command and batch
    mock_fetch_indicators_command = MagicMock()
    mock_fetch_indicators_command.return_value = [{"indicator1": "value1"}, {"indicator2": "value2"}]

    mock_batch = MagicMock()
    mock_batch.side_effect = lambda indicators, batch_size: [indicators[:batch_size]]

    with patch("FeedCyberint.fetch_indicators_command", mock_fetch_indicators_command), patch("FeedCyberint.batch", mock_batch):
        FeedCyberint.main()

    # Assertions
    mock_batch.assert_called_once_with([{"indicator1": "value1"}, {"indicator2": "value2"}], batch_size=5000)
    mock_demisto.createIndicators.assert_called_once_with([{"indicator1": "value1"}, {"indicator2": "value2"}])


@patch("FeedCyberint.demisto")
@patch("FeedCyberint.Client.retrieve_indicators_from_api")
def test_process_feed_response_valid(mock_demisto, mock_client):
    """Test process_feed_response with valid JSON and valid indicators."""

    # Mock auto_detect_indicator_type
    mock_auto_detect = MagicMock(return_value=True)
    with patch("FeedCyberint.auto_detect_indicator_type", mock_auto_detect):
        result = mock_client.process_feed_response("2025-01-01", 100, 0)

    assert len(result) == 0


@patch("FeedCyberint.demisto")
@patch("FeedCyberint.Client.retrieve_indicators_from_api")
def test_process_feed_response_invalid_json(mock_retrieve_indicators, mock_demisto, mock_client):
    """Test process_feed_response with invalid JSON."""
    # Mock invalid JSON response from retrieve_indicators_from_api
    mock_response = '{"ioc_value": "indicator1"}\n{invalid json}'
    mock_retrieve_indicators.return_value = mock_response

    # Mock demisto.error to capture error logs
    mock_demisto.error = MagicMock()

    result = mock_client.process_feed_response("2025-01-01", 100, 0)

    # Assertions
    assert result == []  # Should return an empty list on failure


@patch("FeedCyberint.demisto")
@patch("FeedCyberint.Client.retrieve_indicators_from_api")
def test_process_feed_response_no_indicators(mock_retrieve_indicators, mock_demisto, mock_client):
    """Test process_feed_response when no indicators are returned."""
    # Mock response with no indicators
    mock_response = ""
    mock_retrieve_indicators.return_value = mock_response

    result = mock_client.process_feed_response("2025-01-01", 100, 0)

    # Assertions
    assert result == []  # Should return an empty list if no indicators


@patch("FeedCyberint.demisto")
@patch("FeedCyberint.Client.retrieve_indicators_from_api")
def test_process_feed_response_valid_but_no_matching_indicators(mock_retrieve_indicators, mock_demisto, mock_client):
    """Test process_feed_response with valid JSON, but no matching indicators."""
    # Mock valid JSON response with non-matching indicators
    mock_response = '{"ioc_value": "invalid_indicator"}\n{"ioc_value": "another_invalid_indicator"}'
    mock_retrieve_indicators.return_value = mock_response

    # Mock auto_detect_indicator_type to return False for all values
    mock_auto_detect = MagicMock(return_value=False)
    with patch("FeedCyberint.auto_detect_indicator_type", mock_auto_detect):
        result = mock_client.process_feed_response("2025-01-01", 100, 0)

    # Assertions
    mock_retrieve_indicators.assert_called_once_with("2025-01-01", 100, 0)
    mock_auto_detect.assert_any_call("invalid_indicator")
    mock_auto_detect.assert_any_call("another_invalid_indicator")
    assert result == []  # Should return an empty list since no valid indicators matched


@patch("FeedCyberint.demisto")
@patch("FeedCyberint.Client.retrieve_indicators_from_api")
def test_process_feed_response_valid_with_matching_indicators(mock_retrieve_indicators, mock_demisto, mock_client):
    """Test process_feed_response with valid JSON and matching indicators."""
    # Mock valid JSON response with matching indicators
    mock_response = '{"ioc_value": "valid_indicator"}\n{"ioc_value": "another_valid_indicator"}'
    mock_retrieve_indicators.return_value = mock_response

    # Mock auto_detect_indicator_type to return True for all values
    mock_auto_detect = MagicMock(return_value=True)
    with patch("FeedCyberint.auto_detect_indicator_type", mock_auto_detect):
        result = mock_client.process_feed_response("2025-01-01", 100, 0)

    # Assertions
    mock_retrieve_indicators.assert_called_once_with("2025-01-01", 100, 0)
    mock_auto_detect.assert_any_call("valid_indicator")
    mock_auto_detect.assert_any_call("another_valid_indicator")
    assert len(result) == 2
    assert result[0]["ioc_value"] == "valid_indicator"
    assert result[1]["ioc_value"] == "another_valid_indicator"


@patch("FeedCyberint.tableToMarkdown")
def test_get_indicators_command_with_invalid_limit(mock_client):
    """Test get_indicators_command when the limit argument is invalid."""

    # Mock args input with invalid limit
    args = {"date": "2025-01-01", "limit": None, "offset": 0}

    # Call the function, limit should be parsed as 0

    with pytest.raises(
        TypeError, match=r"int\(\) argument must be a string, a bytes-like object or a real number, not 'NoneType'"
    ):
        FeedCyberint.get_indicators_command(mock_client, args)


def test_process_feed_response_wrong_data(mock_client, requests_mock, capfd):
    with capfd.disabled():
        date_time = "2025-01-02"
        limit = 10
        offset = 0
        mock_response = "test"

        # Mock the HTTP request
        url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
        requests_mock.get(
            f"{BASE_URL}{url_suffix}",
            text=mock_response,
            status_code=200,
        )

        # Call the method with test data
        mock_auto_detect = MagicMock(return_value=True)
        with patch("FeedCyberint.auto_detect_indicator_type", mock_auto_detect):
            result = FeedCyberint.Client.process_feed_response(mock_client, date_time="2025-01-02", limit=10, offset=0)

        # Assert that the result is an empty list (i.e., no indicators found)
        assert result == []


def test_get_indicators_command_ok(mock_client, requests_mock):
    expected_output = "Human-readable Markdown output"
    date_time = "2025-01-02"
    limit = 10
    offset = 0

    # Mock the response from process_feed_response
    mock_response = "[{'detected_activity': 'activity_1}]"

    # Mock the HTTP request
    url_suffix = f"/ioc/api/v1/feed/daily/{date_time}?limit={limit}&offset={offset}"
    requests_mock.get(
        f"{BASE_URL}{url_suffix}",
        text=mock_response,
        status_code=200,
    )

    with (
        patch.object(FeedCyberint, "get_indicators_command", return_value=expected_output),
        patch("CommonServerPython.tableToMarkdown"),
    ):
        # Define the arguments for the command
        args = {"date": "2025-01-02", "limit": 2, "offset": 0}

        # Call the function
        result = FeedCyberint.get_indicators_command(mock_client, args)

        # Validate the CommandResults
        assert result == expected_output


def test_test_module_feed_enabled_success(mock_client):
    """Test test_module with feed_enabled=True and successful request_daily_feed call."""
    # Mock successful request_daily_feed
    mock_client.request_daily_feed = MagicMock(return_value=[])

    result = FeedCyberint.test_module(mock_client, feed_enabled=True)

    assert result == "ok"
    mock_client.request_daily_feed.assert_called_once_with(limit=10, test=True)


def test_test_module_feed_disabled_success(mock_client):
    """Test test_module with feed_enabled=False and successful retrieve_domain_from_api call."""
    # Mock successful retrieve_domain_from_api
    mock_client.retrieve_domain_from_api = MagicMock(return_value={})

    result = FeedCyberint.test_module(mock_client, feed_enabled=False)

    assert result == "ok"
    mock_client.retrieve_domain_from_api.assert_called_once_with("checkpoint.com")


def test_test_module_feed_enabled_unauthorized_error(mock_client):
    """Test test_module with feed_enabled=True and unauthorized error."""
    # Mock `request_daily_feed` to raise a DemistoException with UNAUTHORIZED status
    exception = DemistoException("Unauthorized")
    exception.res = MagicMock(status_code=401)
    mock_client.request_daily_feed = MagicMock(side_effect=exception)

    result = FeedCyberint.test_module(mock_client, feed_enabled=True)

    assert result == "Authorization Error: invalid `API Token`"
    mock_client.request_daily_feed.assert_called_once_with(limit=10, test=True)


def test_test_module_feed_enabled_forbidden_error(mock_client):
    """Test test_module with feed_enabled=True and forbidden error."""
    # Mock `request_daily_feed` to raise a DemistoException with FORBIDDEN status
    exception = DemistoException("Forbidden")
    exception.res = MagicMock(status_code=403)
    mock_client.request_daily_feed = MagicMock(side_effect=exception)

    result = FeedCyberint.test_module(mock_client, feed_enabled=True)

    assert result == "Authorization Error: invalid `API Token`"
    mock_client.request_daily_feed.assert_called_once_with(limit=10, test=True)


def test_test_module_feed_disabled_unauthorized_error(mock_client):
    """Test test_module with feed_enabled=False and unauthorized error."""
    # Mock `retrieve_domain_from_api` to raise a DemistoException with UNAUTHORIZED status
    exception = DemistoException("Unauthorized")
    exception.res = MagicMock(status_code=401)
    mock_client.retrieve_domain_from_api = MagicMock(side_effect=exception)

    result = FeedCyberint.test_module(mock_client, feed_enabled=False)

    assert result == "Authorization Error: invalid `API Token`"
    mock_client.retrieve_domain_from_api.assert_called_once_with("checkpoint.com")


def test_test_module_feed_disabled_forbidden_error(mock_client):
    """Test test_module with feed_enabled=False and forbidden error."""
    # Mock `retrieve_domain_from_api` to raise a DemistoException with FORBIDDEN status
    exception = DemistoException("Forbidden")
    exception.res = MagicMock(status_code=403)
    mock_client.retrieve_domain_from_api = MagicMock(side_effect=exception)

    result = FeedCyberint.test_module(mock_client, feed_enabled=False)

    assert result == "Authorization Error: invalid `API Token`"
    mock_client.retrieve_domain_from_api.assert_called_once_with("checkpoint.com")


def test_test_module_feed_enabled_other_error(mock_client):
    """Test test_module with feed_enabled=True and non-auth related error."""
    # Mock `request_daily_feed` to raise a DemistoException with a different status code
    exception = DemistoException("Internal Server Error")
    exception.res = MagicMock(status_code=500)
    mock_client.request_daily_feed = MagicMock(side_effect=exception)

    with pytest.raises(DemistoException, match="Internal Server Error"):
        FeedCyberint.test_module(mock_client, feed_enabled=True)

    mock_client.request_daily_feed.assert_called_once_with(limit=10, test=True)


def test_test_module_feed_disabled_other_error(mock_client):
    """Test test_module with feed_enabled=False and non-auth related error."""
    # Mock `retrieve_domain_from_api` to raise a DemistoException with a different status code
    exception = DemistoException("Internal Server Error")
    exception.res = MagicMock(status_code=500)
    mock_client.retrieve_domain_from_api = MagicMock(side_effect=exception)

    with pytest.raises(DemistoException, match="Internal Server Error"):
        FeedCyberint.test_module(mock_client, feed_enabled=False)

    mock_client.retrieve_domain_from_api.assert_called_once_with("checkpoint.com")


def test_fetch_indicators_with_filters(mock_client):
    """Test fetch_indicators with various filters applied."""
    # Mock the request_daily_feed to return test data
    mock_indicators = [
        {
            "ioc_value": "malicious.com",
            "ioc_type": "Domain",
            "detected_activity": "phishing",
            "confidence": 90,
            "severity_score": 8,
            "observation_date": "2025-01-01",
            "description": "Phishing domain",
        },
        {
            "ioc_value": "1.2.3.4",
            "ioc_type": "IP",
            "detected_activity": "malware",
            "confidence": 50,
            "severity_score": 5,
            "observation_date": "2025-01-01",
            "description": "Malware IP",
        },
        {
            "ioc_value": "safe.com",
            "ioc_type": "Domain",
            "detected_activity": "phishing",
            "confidence": 30,
            "severity_score": 3,
            "observation_date": "2025-01-01",
            "description": "Low confidence domain",
        },
    ]

    with (
        patch.object(mock_client, "request_daily_feed", return_value=mock_indicators),
        patch("FeedCyberint.auto_detect_indicator_type") as mock_auto_detect,
    ):
        mock_auto_detect.side_effect = lambda x: "Domain" if "." in x and not x[0].isdigit() else "IP"

        result = FeedCyberint.fetch_indicators(
            client=mock_client,
            tlp_color="RED",
            feed_names=["phishing"],
            indicator_types=["Domain"],
            confidence_from=60,
            severity_from=7,
            feed_tags=["test"],
            limit=10,
        )

        # Should only return the first indicator that matches all filters
        assert len(result) == 1
        assert result[0]["value"] == "malicious.com"
        assert result[0]["fields"]["trafficlightprotocol"] == "RED"
        assert "test" in result[0]["fields"]["tags"]


def test_fetch_indicators_with_all_types(mock_client):
    """Test fetch_indicators with 'All' indicator types and feed names."""
    mock_indicators = [
        {
            "ioc_value": "test.com",
            "ioc_type": "Domain",
            "detected_activity": "phishing",
            "confidence": 80,
            "severity_score": 8,
            "observation_date": "2025-01-01",
            "description": "Test domain",
        }
    ]

    with (
        patch.object(mock_client, "request_daily_feed", return_value=mock_indicators),
        patch("FeedCyberint.auto_detect_indicator_type", return_value="Domain"),
    ):
        result = FeedCyberint.fetch_indicators(
            client=mock_client,
            tlp_color="",
            feed_names=["All"],
            indicator_types=["All"],
            confidence_from=0,
            severity_from=0,
            limit=-1,
        )

        assert len(result) == 1
        assert result[0]["value"] == "test.com"


def test_fetch_indicators_limit_reached(mock_client):
    """Test fetch_indicators stops when limit is reached."""
    mock_indicators = [
        {
            "ioc_value": f"test{i}.com",
            "ioc_type": "Domain",
            "detected_activity": "phishing",
            "confidence": 80,
            "severity_score": 8,
            "observation_date": "2025-01-01",
            "description": f"Test domain {i}",
        }
        for i in range(10)
    ]

    with (
        patch.object(mock_client, "request_daily_feed", return_value=mock_indicators),
        patch("FeedCyberint.auto_detect_indicator_type", return_value="Domain"),
    ):
        result = FeedCyberint.fetch_indicators(
            client=mock_client,
            tlp_color="",
            feed_names=["All"],
            indicator_types=["All"],
            confidence_from=0,
            severity_from=0,
            limit=5,
        )

        assert len(result) == 5


def test_fetch_indicators_no_type_detected(mock_client):
    """Test fetch_indicators skips indicators with no detected type."""
    mock_indicators = [
        {
            "ioc_value": "invalid",
            "ioc_type": "Unknown",
            "detected_activity": "phishing",
            "confidence": 80,
            "severity_score": 8,
            "observation_date": "2025-01-01",
            "description": "Invalid indicator",
        }
    ]

    with (
        patch.object(mock_client, "request_daily_feed", return_value=mock_indicators),
        patch("FeedCyberint.auto_detect_indicator_type", return_value=None),
    ):
        result = FeedCyberint.fetch_indicators(
            client=mock_client, tlp_color="", feed_names=["All"], indicator_types=["All"], confidence_from=0, severity_from=0
        )

        assert len(result) == 0


def test_get_url_command_with_activities_and_entities(mock_client):
    """Test get_url_command with detected activities and related entities."""
    mock_response = {
        "data": {
            "entity": {"type": "url", "value": "http://malicious.com"},
            "risk": {
                "malicious_score": 95,
                "occurrences_count": 10,
                "detected_activities": [
                    {
                        "type": "phishing",
                        "observation_date": "2025-01-01",
                        "description": "Phishing activity",
                        "confidence": 90,
                        "occurrences_count": 5,
                    }
                ],
                "related_entities": [{"entity_id": "123", "entity_type": "domain", "entity_name": "malicious.com"}],
            },
            "enrichment": {
                "ips": ["1.2.3.4"],
                "hostname": "malicious.com",
                "domain": "malicious.com",
                "related_entities": [{"entity_id": "456", "entity_type": "ip", "entity_name": "1.2.3.4"}],
            },
            "benign": False,
        }
    }

    mock_client.retrieve_url_from_api = MagicMock(return_value=mock_response)

    result = FeedCyberint.get_url_command(mock_client, {"value": "http://malicious.com"})

    assert result.outputs["entity"]["value"] == "http://malicious.com"
    assert result.outputs["risk"]["malicious_score"] == 95


def test_get_ipv4_command_with_geo_and_asn(mock_client):
    """Test get_ipv4_command with geo and ASN data."""
    mock_response = {
        "data": {
            "entity": {"type": "ipv4", "value": "1.2.3.4"},
            "risk": {"malicious_score": 85, "occurrences_count": 15, "detected_activities": [], "related_entities": []},
            "enrichment": {
                "geo": {"country": "US", "city": "New York"},
                "asn": {"number": "12345", "organization": "Test ISP"},
                "suspicious_urls": ["http://dummy.com"],
                "suspicious_domains": ["dummy.com"],
            },
            "benign": False,
        }
    }

    mock_client.retrieve_ipv4_from_api = MagicMock(return_value=mock_response)

    result = FeedCyberint.get_ipv4_command(mock_client, {"value": "1.2.3.4"})

    assert result.outputs["entity"]["value"] == "1.2.3.4"
    assert result.outputs["enrichment"]["geo"]["country"] == "US"
    assert result.outputs["enrichment"]["asn"]["number"] == "12345"


def test_get_domain_command_with_whois(mock_client):
    """Test get_domain_command with WHOIS data."""
    mock_response = {
        "data": {
            "entity": {"type": "domain", "value": "test.com"},
            "risk": {"malicious_score": 70, "occurrences_count": 8, "detected_activities": [], "related_entities": []},
            "enrichment": {
                "ips": ["1.2.3.4"],
                "whois": {
                    "registrant_name": "John Doe",
                    "registrant_email": "john@example.com",
                    "registrant_organization": "Test Org",
                    "registrant_country": "US",
                    "registrant_telephone": "+1234567890",
                    "technical_contact_email": "tech@example.com",
                    "technical_contact_name": "Tech Person",
                    "technical_contact_organization": "Tech Org",
                    "registrar_name": "Test Registrar",
                    "admin_contact_name": "Admin Person",
                    "admin_contact_organization": "Admin Org",
                    "admin_contact_email": "admin@example.com",
                    "created_date": "2020-01-01",
                    "updated_date": "2024-01-01",
                    "expiration_date": "2026-01-01",
                },
            },
            "benign": False,
        }
    }

    mock_client.retrieve_domain_from_api = MagicMock(return_value=mock_response)

    result = FeedCyberint.get_domain_command(mock_client, {"value": "test.com"})

    assert result.outputs["entity"]["value"] == "test.com"
    assert result.outputs["enrichment"]["whois"]["registrant_name"] == "John Doe"


def test_get_file_sha256_command_with_activities(mock_client):
    """Test get_file_sha256_command with detected activities."""
    mock_response = {
        "data": {
            "entity": {"type": "file", "value": "abc123"},
            "risk": {
                "malicious_score": 95,
                "detected_activities": [
                    {
                        "type": "malware",
                        "observation_date": "2025-01-01",
                        "description": "Malware detected",
                        "confidence": 95,
                        "occurrences_count": 3,
                    }
                ],
                "related_entities": [{"entity_id": "789", "entity_type": "domain", "entity_name": "malware.com"}],
            },
            "enrichment": {
                "filenames": ["malware.exe", "virus.dll"],
                "first_seen": "2024-12-01",
                "download_urls": ["http://dummy.com/malware.exe"],
            },
            "benign": False,
        }
    }

    mock_client.retrieve_file_sha256_from_api = MagicMock(return_value=mock_response)

    result = FeedCyberint.get_file_sha256_command(mock_client, {"value": "abc123"})

    assert result.outputs["entity"]["value"] == "abc123"
    assert len(result.outputs["enrichment"]["filenames"]) == 2


def test_fetch_indicators_command_feed_disabled(mock_client):
    """Test fetch_indicators_command when feed is disabled."""
    params = {
        "feed": False,
        "tlp_color": "RED",
        "feedTags": "tag1",
        "severity_from": "0",
        "confidence_from": "0",
        "feed_name": "All",
        "indicator_type": "All",
        "feedFetchInterval": "30",
    }

    result = FeedCyberint.fetch_indicators_command(mock_client, params)

    assert result == []


def test_fetch_indicators_command_with_yesterday(mock_client):
    """Test fetch_indicators_command fetches from yesterday when needed."""
    params = {
        "feed": True,
        "tlp_color": "RED",
        "feedTags": "tag1",
        "severity_from": "0",
        "confidence_from": "0",
        "feed_name": "All",
        "indicator_type": "All",
        "feedFetchInterval": "1440",
    }

    mock_indicators = [{"indicator": "test.com", "type": "Domain"}]

    with (
        patch("FeedCyberint.is_x_minutes_ago_yesterday", return_value=True),
        patch("FeedCyberint.get_yesterday_time", return_value="2024-12-31"),
        patch("FeedCyberint.fetch_indicators", return_value=mock_indicators),
    ):
        result = FeedCyberint.fetch_indicators_command(mock_client, params)

        assert len(result) == 2  # Yesterday + today


def test_process_feed_response_empty_feeds(mock_client, capfd):
    """Test process_feed_response handles empty response."""
    with capfd.disabled(), patch.object(mock_client, "retrieve_indicators_from_api", return_value=""):
        result = mock_client.process_feed_response("2025-01-01", 100, 0)

        assert result == []


def test_get_today_time_format():
    """Test get_today_time returns correctly formatted date."""
    result = FeedCyberint.get_today_time()

    # Should match DATE_FORMAT which is "%Y-%m-%d"
    assert re.match(r"\d{4}-\d{2}-\d{2}", result)

    # Should be today's date
    today = datetime.now().strftime("%Y-%m-%d")
    assert result == today


def test_is_execution_time_exceeded_exact_limit():
    """Test is_execution_time_exceeded at exact timeout limit."""
    start_time = datetime.now() - timedelta(seconds=FeedCyberint.EXECUTION_TIMEOUT_SECONDS)
    result = FeedCyberint.is_execution_time_exceeded(start_time)

    # Should return False at exactly the limit (not exceeded yet)
    assert result is False


def test_main_not_implemented_command():
    """Test main() raises NotImplementedError for unknown commands."""
    with patch("FeedCyberint.demisto") as mock_demisto:
        mock_demisto.params.return_value = {
            "url": "https://example.com",
            "access_token": {"password": "test-token"},
            "insecure": False,
            "proxy": False,
        }
        mock_demisto.command.return_value = "unknown-command"

        with patch("FeedCyberint.return_error") as mock_return_error:
            FeedCyberint.main()

            mock_return_error.assert_called_once()
            assert "not implemented" in mock_return_error.call_args[0][0].lower()


def test_main_exception_handling():
    """Test main() handles exceptions properly."""
    with patch("FeedCyberint.demisto") as mock_demisto:
        mock_demisto.params.return_value = {
            "url": "https://example.com",
            "access_token": {"password": "test-token"},
            "insecure": False,
            "proxy": False,
        }
        mock_demisto.command.return_value = "test-module"

        with patch("FeedCyberint.Client") as mock_client_class:
            mock_client_class.side_effect = Exception("Connection failed")

            with patch("FeedCyberint.return_error") as mock_return_error:
                FeedCyberint.main()

                mock_return_error.assert_called_once()
                assert "connection failed" in mock_return_error.call_args[0][0].lower()


def test_get_cve_command(mock_client, requests_mock):
    """
    Scenario: Enrich a CVE with Cyberint vulnerability intelligence.
    Given:
     - A valid CVE identifier.
    When:
     - get_cve_command is called.
    Then:
     - Ensure the outputs prefix and key CVE fields are correct.
     - Ensure the readable output references the CVE.
    """
    import json

    with open("test_data/cve_intelligence.json") as file:
        mock_response = json.load(file)
    requests_mock.get(f"{BASE_URL}/cve-intel/external/api/v1/vulnerability/CVE-2024-1234", json=mock_response)

    results = FeedCyberint.get_cve_command(mock_client, {"cve_id": "CVE-2024-1234"})

    assert len(results) == 1
    assert results[0].outputs_prefix == "Cyberint.CVE"
    assert results[0].outputs["cve_id"] == "CVE-2024-1234"
    assert results[0].outputs["cyberint_score"] == 8.7
    assert results[0].outputs["epss"] == 0.974
    assert "Cyberint CVE Intelligence: CVE-2024-1234" in results[0].readable_output


def test_get_cve_command_multiple(mock_client, requests_mock):
    """
    Scenario: Enrich several CVEs in a single command call.
    Given:
     - A comma-separated list of CVE identifiers.
    When:
     - get_cve_command is called.
    Then:
     - Ensure one CommandResults entry is returned per CVE.
    """
    import json

    with open("test_data/cve_intelligence.json") as file:
        mock_response = json.load(file)
    requests_mock.get(f"{BASE_URL}/cve-intel/external/api/v1/vulnerability/CVE-2024-1234", json=mock_response)
    requests_mock.get(f"{BASE_URL}/cve-intel/external/api/v1/vulnerability/CVE-2024-5678", json=mock_response)

    results = FeedCyberint.get_cve_command(mock_client, {"cve_id": "CVE-2024-1234,CVE-2024-5678"})

    assert len(results) == 2


def test_get_cve_command_no_cve(mock_client):
    """
    Scenario: Call the CVE enrich command without a CVE identifier.
    Given:
     - No 'cve_id' argument.
    When:
     - get_cve_command is called.
    Then:
     - Ensure a DemistoException is raised.
    """
    with pytest.raises(DemistoException):
        FeedCyberint.get_cve_command(mock_client, {})


def test_pick_cvss_collection_prefers_v4():
    """
    Scenario: Pick the highest-priority CVSS collection (v4 over v3 over v2).
    Given:
     - A CVSS object containing v3 and v4 collections, each with a base_score.
    When:
     - pick_cvss_collection is called.
    Then:
     - Ensure the v4 base_score, version and vector_string are returned together.
    """
    cvss = {
        "cvss_v3": {"base_score": 9.8, "version": "3.1", "vector_string": "CVSS:3.1/AV:N/.."},
        "cvss_v4": {"base_score": 9.3, "version": "4.0", "vector_string": "CVSS:4.0/AV:N/.."},
    }
    base_score, version, vector_string = FeedCyberint.pick_cvss_collection(cvss)
    assert base_score == 9.3
    assert version == "4.0"
    assert vector_string == "CVSS:4.0/AV:N/.."


def test_pick_cvss_collection_falls_back_to_v3():
    """
    Scenario: Fall back to CVSS v3 when v4 is absent.
    Given:
     - A CVSS object containing only a v3 collection.
    When:
     - pick_cvss_collection is called.
    Then:
     - Ensure the v3 base_score, version and vector_string are returned together.
    """
    cvss = {
        "cvss_v3": {"base_score": 9.8, "version": "3.1", "vector_string": "CVSS:3.1/AV:N/.."},
    }
    base_score, version, vector_string = FeedCyberint.pick_cvss_collection(cvss)
    assert base_score == 9.8
    assert version == "3.1"
    assert vector_string == "CVSS:3.1/AV:N/.."


def test_pick_cvss_collection_falls_back_to_v2():
    """
    Scenario: Fall back to CVSS v2 when v4 and v3 are absent.
    Given:
     - A CVSS object containing only a v2 collection.
    When:
     - pick_cvss_collection is called.
    Then:
     - Ensure the v2 base_score, version and vector_string are returned together.
    """
    cvss = {
        "cvss_v2": {"base_score": 7.5, "version": "2.0", "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P"},
    }
    base_score, version, vector_string = FeedCyberint.pick_cvss_collection(cvss)
    assert base_score == 7.5
    assert version == "2.0"
    assert vector_string == "AV:N/AC:L/Au:N/C:P/I:P/A:P"


def test_pick_cvss_collection_no_data():
    """
    Scenario: No CVSS collection available.
    Given:
     - An empty CVSS object.
    When:
     - pick_cvss_collection is called.
    Then:
     - Ensure the function returns a triple of Nones.
    """
    assert FeedCyberint.pick_cvss_collection({}) == (None, None, None)


def test_get_leaked_credentials_by_domain_command(mock_client, requests_mock):
    """
    Scenario: Look up leaked credentials for a company domain.
    Given:
     - A valid company domain.
    When:
     - get_leaked_credentials_by_domain_command is called.
    Then:
     - Ensure the employee and customer totals are correct.
     - Ensure plaintext passwords are not exposed in the readable output.
    """
    import json

    with open("test_data/leaked_credentials.json") as file:
        mock_response = json.load(file)
    requests_mock.post(f"{BASE_URL}/exposed-credentials/by_domain/", json=mock_response)

    result = FeedCyberint.get_leaked_credentials_by_domain_command(mock_client, {"domain": "example.com"})

    assert result.outputs_prefix == "Cyberint.LeakedCredential"
    assert result.outputs["domain"] == "example.com"
    assert result.outputs["employee_total"] == 2
    assert result.outputs["customer_total"] == 1
    assert result.outputs["total"] == 3
    assert "Summer2024!" not in result.readable_output


def test_get_leaked_credentials_by_domain_command_limit(mock_client, requests_mock):
    """
    Scenario: Limit the number of leaked credential records returned.
    Given:
     - A 'limit' argument of 1.
    When:
     - get_leaked_credentials_by_domain_command is called.
    Then:
     - Ensure only the requested number of records per type is returned.
    """
    import json

    with open("test_data/leaked_credentials.json") as file:
        mock_response = json.load(file)
    requests_mock.post(f"{BASE_URL}/exposed-credentials/by_domain/", json=mock_response)

    result = FeedCyberint.get_leaked_credentials_by_domain_command(mock_client, {"domain": "example.com", "limit": "1"})

    assert result.outputs["employee_total"] == 1
    assert result.outputs["customer_total"] == 1


def test_get_leaked_credentials_by_domain_command_last_seen_filter(mock_client, requests_mock):
    """
    Scenario: Filter leaked credentials by their last-seen date.
    Given:
     - A 'last_seen_from' argument.
    When:
     - get_leaked_credentials_by_domain_command is called.
    Then:
     - Ensure only credentials last seen on or after the date are returned.
    """
    import json

    with open("test_data/leaked_credentials.json") as file:
        mock_response = json.load(file)
    requests_mock.post(f"{BASE_URL}/exposed-credentials/by_domain/", json=mock_response)

    result = FeedCyberint.get_leaked_credentials_by_domain_command(
        mock_client, {"domain": "example.com", "last_seen_from": "2024-05-15T00:00:00Z"}
    )

    assert result.outputs["employee_total"] == 1
    assert result.outputs["customer_total"] == 1


def test_get_leaked_credentials_by_domain_command_no_domain(mock_client):
    """
    Scenario: Call the credential leak lookup command without a domain.
    Given:
     - No 'domain' argument.
    When:
     - get_leaked_credentials_by_domain_command is called.
    Then:
     - Ensure a DemistoException is raised.
    """
    with pytest.raises(DemistoException):
        FeedCyberint.get_leaked_credentials_by_domain_command(mock_client, {})