Flashpoint Vulnerability Feed
Flashpoint Vulnerability Feed Integration allows importing vulnerability intelligence from the Flashpoint platform, empowering teams to reduce exposure windows by identifying high-risk vulnerabilities weeks before public sources. This includes independent research, zero-day disclosures, analyst recommendations, affected and non-affected versions, and over 105,000 vulnerabilities not found in the public NVD/CVE feeds database.
Data Enrichment & Threat Intelligence · Flashpoint Vulnerability Feed · Feed
Details
| ID | Flashpoint Vulnerability Feed |
|---|---|
| Provider | Flashpoint |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
Flashpoint Vulnerability Feed Integration allows importing vulnerability intelligence from the Flashpoint platform, empowering teams to reduce exposure windows by identifying high-risk vulnerabilities weeks before public sources. This includes independent research, zero-day disclosures, analyst recommendations, affected and non-affected versions, and over 105,000 vulnerabilities not found in the public NVD/CVE feeds database.
This integration was integrated and tested with API v1 of Flashpoint.
Fetch Indicators
Fetching the Ignite vulnerabilities. The vulnerabilities that are updated after the provided “First fetch time” will be fetched in the ascending order.
Configure Flashpoint Vulnerability Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | Server URL to connect to Flashpoint Ignite. | True |
| API Key | API key used for secure communication with the Flashpoint Ignite platform. | True |
| First Fetch Time | Backfill vulnerabilities by providing date or relative timestamp. Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. Default: 3 days. |
False |
| Fetch indicators | Enable to fetch indicators. | False |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
| Traffic Light Protocol Color | The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. | False |
| Ransomware Score | Filter by Ransomware score. | False |
| Attack Types | Filter by Attack type classification. | False |
| Severity | Filter by severity, which is calculated based on CVSS values. | False |
| Product Names | Filter by associated product names (case-insensitive). | False |
| Vendor Names | Filter by associated vendor names (case-insensitive). | False |
| CWE IDs | Filter by CWE IDs assigned by Mitre. | False |
| Minimum CVSS v2 Score | Filter by lower limit of the CVSS v2 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
False |
| Maximum CVSS v2 Score | Filter by upper limit of the CVSS v2 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
False |
| Minimum CVSS v3 Score | Filter by lower limit of the CVSS v3 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
False |
| Maximum CVSS v3 Score | Filter by upper limit of the CVSS v3 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
False |
| Minimum CVSS v4 Score | Filter by lower limit of the CVSS v4 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
False |
| Maximum CVSS v4 Score | Filter by upper limit of the CVSS v4 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
False |
| Reference Types | Filter by reference types. | False |
| Reference Values | Filter by reference values. Use with Reference Types to filter by specific reference type. | False |
| Locations | Filter by location type classification. | False |
| Minimum EPSS v3 Score | Filter by lower limit of the EPSS v3 score. Example value 0.5 Note: Value must be a float between 0 and 1. |
False |
| Maximum EPSS v3 Score | Filter by upper limit of the EPSS v3 score. Example value 1.0 Note: Value must be a float between 0 and 1. |
False |
| Filter by Tags | Filter vulnerabilities by tags. | False |
| feedExpirationPolicy | False | |
| feedExpirationInterval | False | |
| Feed Fetch Interval | Interval in minutes to fetch indicators. | False |
| Create relationships | Create relationships between indicators as part of Enrichment. | 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 |
| Tags | Provides the tags to be added to the indicators. Supports CSV values. | False |
| Trust any certificate (not secure) | Indicates whether to allow connections without verifying SSL certificate’s validity. | False |
| Use system proxy settings | Indicates whether to use XSOAR’s system proxy settings to connect to the API. | False |
Enable enrichment
To enable enrichment for the “Flashpoint Vulnerability” and “CVE” indicator types to retrieve the latest information, follow the steps below:
- Install the “Flashpoint” content pack or update it to version 2.2.0 or later.
- Configure the “Flashpoint Ignite” integration to allow the execution of enrichment commands. The Fetch Incidents option is not required for this setup.
- [Optional] Open the “Flashpoint Vulnerability” or “CVE” indicator and click the “Enrich” button to retrieve the latest information.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
flashpoint-vulnerability-get-indicators
Retrieves indicators from the Flashpoint Vulnerability Feed. It displays the content of the fetch-indicators command.
Base Command
flashpoint-vulnerability-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| last_touched_after | Get vulnerabilities that were last touched after the specified date or relative timestamp. Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. Default is 3 days. |
Optional |
| last_touched_before | Get vulnerabilities that were last touched before the specified date or relative timestamp. Formats accepted: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. Default is now. |
Optional |
| ransomware_scores | Filter by Ransomware score. Possible values are: Critical, High, Medium, Low. | Optional |
| attack_types | Filter by Attack type classification. Possible values are: Authentication Management, Cryptographic, Infrastructure, Input Manipulation, Misconfiguration, Man-In-The-Middle (MITM), Other, Race Condition, Attack Type Unknown. | Optional |
| severities | Filter by severity, which is calculated based on CVSS values. Possible values are: Critical, High, Medium, Low, Informational. | Optional |
| products | Filter by associated product names (case-insensitive). | Optional |
| vendors | Filter by associated vendor names (case-insensitive). | Optional |
| cwe_ids | Filter by CWE IDs assigned by Mitre. | Optional |
| min_cvssv2_score | Filter by lower limit of the CVSSv2 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
Optional |
| max_cvssv2_score | Filter by upper limit of the CVSSv2 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
Optional |
| min_cvssv3_score | Filter by lower limit of the CVSSv3 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
Optional |
| max_cvssv3_score | Filter by upper limit of the CVSSv3 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
Optional |
| min_cvssv4_score | Filter by lower limit of the CVSSv4 score. Prioritizes Flashpoint generated scores when possible. Example value 7.0 Note: Value must be a float between 0 and 10. |
Optional |
| max_cvssv4_score | Filter by upper limit of the CVSSv4 score. Prioritizes Flashpoint generated scores when possible. Example value 10.0 Note: Value must be a float between 0 and 10. |
Optional |
| ref_types | Filter by reference types. Possible values are: Bug Tracker, Bugtraq ID, CERT, CERT VU, CIAC Advisory, CVE ID, D2 Elliot, DISA IAVA, Exploit Activity, Exploit Database, Flashpoint, Generic Exploit URL, Generic Informational URL, Immunity CANVAS, Immunity CANVAS (D2ExploitPack), Immunity CANVAS (White Phosphorus), ISS X-Force ID, Japan Vulnerability Notes, Keyword, Mail List Post, Metasploit URL, Microsoft Knowledge Base Article, Microsoft Security Bulletin, Nessus Script ID, News Article, Nikto Item ID, Other Advisory URL, Other Solution URL, OVAL ID, Packet Storm, RedHat RHSA, Related VulnDB ID, SCIP VulDB ID, Secunia Advisory ID, Security Tracker, Snort Signature ID, Tenable PVS, US-CERT Cyber Security Alert, Vendor Specific Advisory URL, Vendor Specific Solution URL, Vendor URL, Vendor Specific News/Changelog Entry, VUPEN Advisory. | Optional |
| ref_values | Filter by reference values. Use with Reference Types to filter by specific reference type. | Optional |
| locations | Filter by location type classification. Possible values are: Context Dependent, Dial-up Access Required, Local Access Required, Legacy: Local / Remote, Mobile Phone / Hand-held Device, Physical Access Required, Remote / Network Access, Location Unknown, Wireless Vector. | Optional |
| min_epss_score | Filter by lower limit of the EPSS v3 score. Example value 0.5 Note: Value must be a float between 0 and 1. |
Optional |
| max_epss_score | Filter by upper limit of the EPSS v3 score. Example value 1.0 Note: Value must be a float between 0 and 1. |
Optional |
| tags | Filter vulnerabilities by tags. | Optional |
| limit | The maximum number of indicators to return. Default is 50. | Optional |
| from | Offset for pagination. Default is 0. | Optional |
Context Output
There is no context output for this command.
Command Example
!flashpoint-vulnerability-get-indicators limit=1
Human Readable Output
Vulnerabilities
ID Indicator Value Title Vulnerability Status EPSS Score Ransomware Score Published Modified Description Solution Technical Description Tags CVSS v2 CVSS v3 CVSS v4 Products CWEs Exploits Count External References 111111 CVE-YYYY-XXXX Sample Vulnerability Title Active 0.001 None 2026-01-01T00:00:00Z 2026-01-01T00:00:00Z This is a dummy description for testing purposes. Upgrade to a fixed version. Dummy technical details go here. dummy - access_vector: NETWORK
access_complexity: MEDIUM
authentication: SINGLE_INSTANCE
confidentiality_impact: NONE
integrity_impact: NONE
availability_impact: COMPLETE
source: Flashpoint
generated_at: 2026-01-01T00:00:00Z
score: 5.0- attack_vector: NETWORK
attack_complexity: LOW
privileges_required: NONE
user_interaction: NONE
scope: UNCHANGED
confidentiality_impact: NONE
integrity_impact: NONE
availability_impact: HIGH
source: NVD
generated_at: 2026-01-01T00:00:00Z
score: 7.5
vector_string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
version: 3.1
updated_at: 2026-01-01T00:00:00Z
- attack_vector: NETWORK
attack_complexity: LOW
privileges_required: NONE
user_interaction: NONE
scope: UNCHANGED
confidentiality_impact: HIGH
integrity_impact: LOW
availability_impact: NONE
source: Flashpoint
generated_at: 2026-01-01T00:00:00Z
score: 8.2
vector_string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
version: 3.1
updated_at: 2026-01-01T00:00:00Z- score: 7.1
threat_score: 7.1
source: NVD
generated_at: 2026-01-01T00:00:00Z
updated_at: 2026-01-01T00:00:00Z
vector_string: CVSS:4.0/AV:N/AC:L/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
version: 4.0
attack_vector: NETWORK
attack_complexity: LOW
attack_requirements: NONE
privileges_required: NONE
user_interaction: NONE
exploit_maturity: NOT_DEFINED
vulnerable_system_confidentiality_impact: NONE
vulnerable_system_integrity_impact: NONE
vulnerable_system_availability_impact: HIGH
subsequent_system_confidentiality_impact: NONE
subsequent_system_integrity_impact: NONE
subsequent_system_availability_impact: NONE
- score: 9.2
threat_score: 9.2
source: Flashpoint
generated_at: 2026-01-01T00:00:00Z
updated_at: 2026-01-01T00:00:00Z
vector_string: CVSS:4.0/AV:N/AC:L/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N
version: 4.0
attack_vector: NETWORK
attack_complexity: LOW
attack_requirements: NONE
privileges_required: NONE
user_interaction: NONE
exploit_maturity: NOT_DEFINED
vulnerable_system_confidentiality_impact: HIGH
vulnerable_system_integrity_impact: LOW
vulnerable_system_availability_impact: NONE
subsequent_system_confidentiality_impact: NONE
subsequent_system_integrity_impact: NONE
subsequent_system_availability_impact: NONE- id: 123
name: Sample Product
versions:
- id: 456
vulndb_version_id: 789
name: 1.0.0
affected: Affected
all_prior_versions_affected: false
cpes:
- name: cpe:2.3:a:vendor:product:1.0.0:::::::
*source: Flashpoint-specified
- name: cpe:2.3:a:vendor:product:1.0.1:::::::
*source: Flashpoint-specified
vendor_id: 456
vendor: Sample Vendor- cwe_id: 999
name: Dummy CWE
source: example
cve_ids: CVE-YYYY-XXXX0 - value: https://example.com/advisory
type: Vendor Specific Advisory URL
url: https://example.com/advisory
created_at: 2026-01-01T00:00:00Z
Configuration parameters
url— Server URL (required)credentials— (required)first_fetch— First Fetch Timefeed— Fetch indicatorsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol Colorransomware_scores— Ransomware Scoreattack_types— Attack Typesseverities— Severityproducts— Product Namesvendors— Vendor Namescwe_ids— CWE IDsmin_cvssv2_score— Minimum CVSS v2 Scoremax_cvssv2_score— Maximum CVSS v2 Scoremin_cvssv3_score— Minimum CVSS v3 Scoremax_cvssv3_score— Maximum CVSS v3 Scoremin_cvssv4_score— Minimum CVSS v4 Scoremax_cvssv4_score— Maximum CVSS v4 Scoreref_types— Reference Typesref_values— Reference Valueslocations— Locationsmin_epss_score— Minimum EPSS v3 Scoremax_epss_score— Maximum EPSS v3 Scoretags— Filter by TagsfeedExpirationPolicy—feedExpirationInterval—feedFetchInterval— Feed Fetch IntervalcreateRelationship— Create relationshipsfeedBypassExclusionList— Bypass exclusion listfeedTags— TagsfeedIncremental—insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
flashpoint-vulnerability-get-indicatorsRetrieves indicators from the Flashpoint Vulnerability Feed. It displays the content of the fetch-indicators command.
"""Flashpoint Vulnerability Feed Integration Unit Tests.""" import json import os from unittest.mock import patch import pytest import requests_mock as rm import FeedFlashpointVulnerability from CommonServerPython import DemistoException, CommandResults from FeedFlashpointVulnerability import ( DEFAULT_LIMIT, HTTP_ERRORS, MESSAGES, URL_SUFFIX, Client, create_indicator_from_vulnerability, demisto, fetch_indicators_command, flashpoint_vulnerability_get_indicators_command, main, remove_space_from_args, test_module as main_test_module, validate_fetch_indicators_params, validate_params, PAGE_SIZE, validate_cvss_score, validate_epss_score, remove_nulls_from_nested_dictionary, ) from datetime import UTC, datetime, timedelta """ CONSTANTS """ API_KEY = "test_api_key" MOCK_URL = "https://mock.flashpoint.io" BASIC_PARAMS = { "url": MOCK_URL, "credentials": {"password": API_KEY}, "feedReliability": "A - Completely reliable", } """ UTILITY FUNCTIONS AND FIXTURES """ def util_load_json(path: str) -> dict: """Load a json file to python dict.""" with open(path, encoding="utf-8") as f: return json.loads(f.read()) @pytest.fixture def mock_client(): """Create a mock client object for testing.""" client = Client( base_url=MOCK_URL, api_key=API_KEY, verify=False, proxy=False, ) client.platform_url = MOCK_URL return client @pytest.fixture def requests_mock(): """Create a requests mock fixture.""" with rm.Mocker() as m: yield m """ TEST CASES FOR HELPER FUNCTIONS """ class TestRemoveSpaceFromArgs: """Test cases for remove_space_from_args function.""" def test_remove_space_from_args_with_spaces(self): """ Test remove_space_from_args removes leading/trailing spaces. Given: - Dictionary with string values containing spaces. When: - Calling remove_space_from_args function. Then: - Returns dictionary with trimmed string values. """ args = {"key1": " value1 ", "key2": "value2", "key3": " value3"} result = remove_space_from_args(args) assert result["key1"] == "value1" assert result["key2"] == "value2" assert result["key3"] == "value3" def test_remove_space_from_args_with_non_string_values(self): """ Test remove_space_from_args handles non-string values. Given: - Dictionary with mixed value types. When: - Calling remove_space_from_args function. Then: - Returns dictionary with non-string values unchanged. """ args = {"key1": " value1 ", "key2": 123, "key3": None} result = remove_space_from_args(args) assert result["key1"] == "value1" assert result["key2"] == 123 assert result["key3"] is None class TestGetNestedValue: """Test cases for demisto.get function.""" def test_demisto_get_simple_path(self): """ Test demisto.get with simple path. Given: - Dictionary with nested structure. When: - Calling demisto.get with simple path. Then: - Returns the value at the path. """ data = {"key1": "value1"} result = demisto.get(data, "key1") assert result == "value1" def test_demisto_get_nested_path(self): """ Test demisto.get with nested path. Given: - Dictionary with nested structure. When: - Calling demisto.get with nested path. Then: - Returns the value at the nested path. """ data = {"parent": {"child": {"value": "nested_value"}}} result = demisto.get(data, "parent.child.value") assert result == "nested_value" def test_demisto_get_missing_path(self): """ Test demisto.get with missing path. Given: - Dictionary without the specified path. When: - Calling demisto.get with missing path. Then: - Returns None. """ data = {"key1": "value1"} result = demisto.get(data, "missing.path") assert result is None def test_demisto_get_empty_path(self): """ Test demisto.get with empty path. Given: - Dictionary with data. When: - Calling demisto.get with empty path. Then: - Returns None. """ data = {"key1": "value1"} result = demisto.get(data, "") assert result is None class TestValidateParams: """Test cases for validate_params function.""" def test_validate_params_success(self): """ Test validate_params with valid parameters. Given: - Valid parameters dictionary. When: - Calling validate_params function. Then: - No exception is raised. """ params = BASIC_PARAMS.copy() validate_params(params) # Should not raise def test_validate_params_missing_url(self): """ Test validate_params with missing URL. Given: - Parameters without URL. When: - Calling validate_params function. Then: - Raises DemistoException. """ params = {"credentials": {"password": API_KEY}, "feedReliability": "A - Completely reliable"} with pytest.raises(DemistoException) as err: validate_params(params) assert MESSAGES["NO_PARAM_PROVIDED"].format("Server URL") in str(err.value) def test_validate_params_missing_api_key(self): """ Test validate_params with missing API key. Given: - Parameters without API key. When: - Calling validate_params function. Then: - Raises DemistoException. """ params = {"url": MOCK_URL, "credentials": {}, "feedReliability": "A - Completely reliable"} with pytest.raises(DemistoException) as err: validate_params(params) assert MESSAGES["NO_PARAM_PROVIDED"].format("API Key") in str(err.value) def test_validate_params_empty_api_key(self): """ Test validate_params with empty API key. Given: - Parameters with empty API key. When: - Calling validate_params function. Then: - Raises DemistoException. """ params = {"url": MOCK_URL, "credentials": {"password": " "}, "feedReliability": "A - Completely reliable"} with pytest.raises(DemistoException) as err: validate_params(params) assert MESSAGES["NO_PARAM_PROVIDED"].format("API Key") in str(err.value) def test_validate_params_missing_reliability(self): """ Test validate_params with missing feed reliability. Given: - Parameters without feed reliability. When: - Calling validate_params function. Then: - Raises DemistoException. """ params = {"url": MOCK_URL, "credentials": {"password": API_KEY}} with pytest.raises(DemistoException) as err: validate_params(params) assert MESSAGES["NO_PARAM_PROVIDED"].format("Source Reliability") in str(err.value) class TestCreateIndicatorFromVulnerability: """Test cases for create_indicator_from_vulnerability function.""" def test_create_indicator_with_cve(self): """ Test create_indicator_from_vulnerability with CVE ID. Given: - Vulnerability data with CVE ID. When: - Calling create_indicator_from_vulnerability function. Then: - Returns indicator with CVE type and value. - CVSS fields are populated from Flashpoint source (priority). - CPE list is extracted from products. """ vulnerability = util_load_json("test_data/vulnerability_detail_200.json") remove_nulls_from_nested_dictionary(vulnerability) indicator = util_load_json("test_data/indicator_with_cve.json") result = create_indicator_from_vulnerability(vulnerability, {"createRelationship": "True"}, MOCK_URL) assert result == indicator def test_create_indicator_without_cve(self): """ Test create_indicator_from_vulnerability without CVE ID. Given: - Vulnerability data without CVE ID. When: - Calling create_indicator_from_vulnerability function. Then: - Returns indicator with custom type and FP ID. - CVSS fields are populated from available sources. """ vulnerability = util_load_json("test_data/vulnerability_without_cve_200.json") remove_nulls_from_nested_dictionary(vulnerability) indicator = util_load_json("test_data/indicator_without_cve.json") params: dict = {"feedTags": []} result = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert result == indicator def test_create_indicator_without_tlp_color(self): """ Test create_indicator_from_vulnerability without TLP color. Given: - Vulnerability data and params without TLP color. When: - Calling create_indicator_from_vulnerability function. Then: - Returns indicator without trafficlightprotocol field. """ vulnerability = util_load_json("test_data/vulnerability_detail_200.json") params: dict = {"feedTags": []} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert "trafficlightprotocol" not in indicator["fields"] def test_create_indicator_cvss_priority_flashpoint_source(self): """ Test create_indicator_from_vulnerability prioritizes Flashpoint CVSS source. Given: - Vulnerability data with multiple CVSS sources including Flashpoint. When: - Calling create_indicator_from_vulnerability function. Then: - CVSS fields use Flashpoint source values (9.2) over NVD (7.1). """ vulnerability = util_load_json("test_data/vulnerability_detail_200.json") params: dict = {} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) # Should prioritize Flashpoint source (9.2) over NVD (7.1) assert indicator["fields"]["cvss"] == 9.2 assert indicator["fields"]["cvssscore"] == 9.2 def test_create_indicator_cvss_fallback_to_v2(self): """ Test create_indicator_from_vulnerability falls back to CVSSv2 when v3 unavailable. Given: - Vulnerability data with only CVSSv2 scores. When: - Calling create_indicator_from_vulnerability function. Then: - CVSS fields use CVSSv2 values. """ vulnerability = { "id": 333333, "cve_ids": ["CVE-2024-TEST"], "title": "Test Vulnerability", "description": "Test description", "timelines": {"published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z"}, "tags": [], "cvss_v3s": [], "cvss_v2s": [{"score": 6.4, "version": "2.0", "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:N", "source": "NVD"}], "cvss_v4s": [], "products": [], } params: dict = {} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert indicator["fields"]["cvss"] == 6.4 assert indicator["fields"]["cvssversion"] == "2.0" assert "AV:N" in indicator["fields"]["cvssvector"] def test_create_indicator_cvss_fallback_to_v3(self): """ Test create_indicator_from_vulnerability uses CVSSv3 when v4 unavailable. Given: - Vulnerability data with only CVSSv3 scores. When: - Calling create_indicator_from_vulnerability function. Then: - CVSS fields use CVSSv3 values. """ vulnerability = { "id": 444444, "cve_ids": ["CVE-2024-V4TEST"], "title": "Test Vulnerability V4", "description": "Test description", "timelines": {"published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z"}, "tags": [], "cvss_v2s": [], "cvss_v3s": [ { "score": 9.1, "version": "3.1", "vector_string": "CVSS:3.1/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", "source": "Flashpoint", } ], "cvss_v4s": [], "products": [], } params: dict = {} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert indicator["fields"]["cvss"] == 9.1 assert indicator["fields"]["cvssversion"] == "3.1" assert "CVSS:3.1" in indicator["fields"]["cvssvector"] def test_create_indicator_cpe_extraction_multiple_products(self): """ Test create_indicator_from_vulnerability extracts CPEs from multiple products. Given: - Vulnerability data with multiple products and versions. When: - Calling create_indicator_from_vulnerability function. Then: - All CPEs are extracted into vulnerableproducts field. """ vulnerability = { "id": 555555, "cve_ids": ["CVE-2024-CPE"], "title": "Multi-Product Vulnerability", "description": "Test description", "timelines": {"published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z"}, "tags": [], "cvss_v3s": [], "cvss_v2s": [], "cvss_v4s": [], "products": [ { "id": 1, "name": "Product A", "versions": [ {"version": "1.0", "cpes": [{"name": "cpe:2.3:a:vendor:producta:1.0:*:*:*:*:*:*:*"}]}, {"version": "2.0", "cpes": [{"name": "cpe:2.3:a:vendor:producta:2.0:*:*:*:*:*:*:*"}]}, ], }, { "id": 2, "name": "Product B", "versions": [ {"version": "3.0", "cpes": [{"name": "cpe:2.3:a:vendor:productb:3.0:*:*:*:*:*:*:*"}]}, ], }, ], } params: dict = {} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert len(indicator["fields"]["vulnerableproducts"]) == 3 cpe_names = [cpe["CPE"] for cpe in indicator["fields"]["vulnerableproducts"]] assert "cpe:2.3:a:vendor:producta:1.0:*:*:*:*:*:*:*" in cpe_names assert "cpe:2.3:a:vendor:producta:2.0:*:*:*:*:*:*:*" in cpe_names assert "cpe:2.3:a:vendor:productb:3.0:*:*:*:*:*:*:*" in cpe_names def test_create_indicator_empty_cvss_arrays(self): """ Test create_indicator_from_vulnerability handles empty CVSS arrays. Given: - Vulnerability data with empty CVSS arrays. When: - Calling create_indicator_from_vulnerability function. Then: - CVSS fields are empty strings. """ vulnerability = { "id": 666666, "cve_ids": ["CVE-2024-NOCVSS"], "title": "No CVSS Vulnerability", "description": "Test description", "timelines": {"published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z"}, "tags": [], "cvss_v3s": [], "cvss_v2s": [], "cvss_v4s": [], "products": [], } params: dict = {} indicator = create_indicator_from_vulnerability(vulnerability, params, MOCK_URL) assert indicator["value"] == "CVE-2024-NOCVSS" assert indicator["type"] == "CVE" class TestValidateEpssScore: """Test cases for validate_epss_score function.""" def test_validate_epss_score_with_valid_min_and_max(self): """ Test validate_epss_score with valid min and max values. Given: - Valid min_epss = "0.5" and max_epss = "0.8" When: - Calling validate_epss_score Then: - No exception is raised """ validate_epss_score("0.5", "Minimum EPSS Score", "0.8", "Maximum EPSS Score") def test_validate_epss_score_with_valid_min_only(self): """ Test validate_epss_score with valid min value only. Given: - Valid min_epss = "0.3" and max_epss = None When: - Calling validate_epss_score Then: - No exception is raised """ validate_epss_score("0.3", "Minimum EPSS Score", None, "Maximum EPSS Score") def test_validate_epss_score_with_valid_max_only(self): """ Test validate_epss_score with valid max value only. Given: - min_epss = None and valid max_epss = "0.9" When: - Calling validate_epss_score Then: - No exception is raised """ validate_epss_score(None, "Minimum EPSS Score", "0.9", "Maximum EPSS Score") def test_validate_epss_score_with_both_none(self): """ Test validate_epss_score with both values as None. Given: - min_epss = None and max_epss = None When: - Calling validate_epss_score Then: - No exception is raised """ validate_epss_score(None, "Minimum EPSS Score", None, "Maximum EPSS Score") def test_validate_epss_score_with_integer_values(self): """ Test validate_epss_score with integer values. Given: - min_epss = "0" and max_epss = "1" When: - Calling validate_epss_score Then: - No exception is raised (integers are valid floats) """ validate_epss_score("0", "Minimum EPSS Score", "1", "Maximum EPSS Score") def test_validate_epss_score_with_equal_min_and_max(self): """ Test validate_epss_score with equal min and max values. Given: - min_epss = "0.5" and max_epss = "0.5" When: - Calling validate_epss_score Then: - No exception is raised (equal values are valid) """ validate_epss_score("0.5", "Minimum EPSS Score", "0.5", "Maximum EPSS Score") def test_validate_epss_score_with_invalid_min_non_numeric(self): """ Test validate_epss_score with invalid non-numeric min value. Given: - Invalid min_epss = "abc" When: - Calling validate_epss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_epss_score("abc", "Minimum EPSS Score", "0.8", "Maximum EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Minimum EPSS Score") in str(err.value) def test_validate_epss_score_with_invalid_max_non_numeric(self): """ Test validate_epss_score with invalid non-numeric max value. Given: - Invalid max_epss = "xyz" When: - Calling validate_epss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_epss_score("0.3", "Minimum EPSS Score", "xyz", "Maximum EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Maximum EPSS Score") in str(err.value) def test_validate_epss_score_with_min_greater_than_max(self): """ Test validate_epss_score with min greater than max. Given: - min_epss = "0.9" and max_epss = "0.5" When: - Calling validate_epss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_epss_score("0.9", "Minimum EPSS Score", "0.5", "Maximum EPSS Score") assert MESSAGES["INVALID_SCORE_RANGE"].format("Minimum EPSS Score", "Maximum EPSS Score") in str(err.value) def test_validate_epss_score_with_empty_string_min(self): """ Test validate_epss_score with empty string for min. Given: - min_epss = "" (empty string) When: - Calling validate_epss_score Then: - No exception is raised (empty string is falsy) """ validate_epss_score("", "Minimum EPSS Score", "0.8", "Maximum EPSS Score") def test_validate_epss_score_with_empty_string_max(self): """ Test validate_epss_score with empty string for max. Given: - max_epss = "" (empty string) When: - Calling validate_epss_score Then: - No exception is raised (empty string is falsy) """ validate_epss_score("0.3", "Minimum EPSS Score", "", "Maximum EPSS Score") def test_validate_epss_score_with_special_characters(self): """ Test validate_epss_score with special characters. Given: - min_epss = "0.5@#" When: - Calling validate_epss_score Then: - Raises DemistoException """ with pytest.raises(DemistoException) as err: validate_epss_score("0.5@#", "Minimum EPSS Score", "0.8", "Maximum EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Minimum EPSS Score") in str(err.value) class TestValidateCvssScore: """Test cases for validate_cvss_score function.""" def test_validate_cvss_score_with_valid_min_and_max(self): """ Test validate_cvss_score with valid min and max values. Given: - Valid min_cvss = "5.0" and max_cvss = "8.5" When: - Calling validate_cvss_score Then: - No exception is raised """ validate_cvss_score("5.0", "Minimum CVSS Score", "8.5", "Maximum CVSS Score") def test_validate_cvss_score_with_valid_min_only(self): """ Test validate_cvss_score with valid min value only. Given: - Valid min_cvss = "3.0" and max_cvss = None When: - Calling validate_cvss_score Then: - No exception is raised """ validate_cvss_score("3.0", "Minimum CVSS Score", None, "Maximum CVSS Score") def test_validate_cvss_score_with_valid_max_only(self): """ Test validate_cvss_score with valid max value only. Given: - min_cvss = None and valid max_cvss = "9.0" When: - Calling validate_cvss_score Then: - No exception is raised """ validate_cvss_score(None, "Minimum CVSS Score", "9.0", "Maximum CVSS Score") def test_validate_cvss_score_with_both_none(self): """ Test validate_cvss_score with both values as None. Given: - min_cvss = None and max_cvss = None When: - Calling validate_cvss_score Then: - No exception is raised """ validate_cvss_score(None, "Minimum CVSS Score", None, "Maximum CVSS Score") def test_validate_cvss_score_with_integer_values(self): """ Test validate_cvss_score with integer values. Given: - min_cvss = "0" and max_cvss = "10" When: - Calling validate_cvss_score Then: - No exception is raised (integers are valid floats) """ validate_cvss_score("0", "Minimum CVSS Score", "10", "Maximum CVSS Score") def test_validate_cvss_score_with_equal_min_and_max(self): """ Test validate_cvss_score with equal min and max values. Given: - min_cvss = "7.5" and max_cvss = "7.5" When: - Calling validate_cvss_score Then: - No exception is raised (equal values are valid) """ validate_cvss_score("7.5", "Minimum CVSS Score", "7.5", "Maximum CVSS Score") def test_validate_cvss_score_with_boundary_values(self): """ Test validate_cvss_score with boundary values (0 and 10). Given: - min_cvss = "0.0" and max_cvss = "10.0" When: - Calling validate_cvss_score Then: - No exception is raised """ validate_cvss_score("0.0", "Minimum CVSS Score", "10.0", "Maximum CVSS Score") def test_validate_cvss_score_with_invalid_min_non_numeric(self): """ Test validate_cvss_score with invalid non-numeric min value. Given: - Invalid min_cvss = "invalid" When: - Calling validate_cvss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_cvss_score("invalid", "Minimum CVSS Score", "8.0", "Maximum CVSS Score") assert MESSAGES["INVALID_CVSS_SCORE"].format("Minimum CVSS Score") in str(err.value) def test_validate_cvss_score_with_invalid_max_non_numeric(self): """ Test validate_cvss_score with invalid non-numeric max value. Given: - Invalid max_cvss = "not_a_number" When: - Calling validate_cvss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_cvss_score("5.0", "Minimum CVSS Score", "not_a_number", "Maximum CVSS Score") assert MESSAGES["INVALID_CVSS_SCORE"].format("Maximum CVSS Score") in str(err.value) def test_validate_cvss_score_with_min_greater_than_max(self): """ Test validate_cvss_score with min greater than max. Given: - min_cvss = "9.0" and max_cvss = "5.0" When: - Calling validate_cvss_score Then: - Raises DemistoException with appropriate message """ with pytest.raises(DemistoException) as err: validate_cvss_score("9.0", "Minimum CVSS Score", "5.0", "Maximum CVSS Score") assert MESSAGES["INVALID_SCORE_RANGE"].format("Minimum CVSS Score", "Maximum CVSS Score") in str(err.value) def test_validate_cvss_score_with_empty_string_min(self): """ Test validate_cvss_score with empty string for min. Given: - min_cvss = "" (empty string) When: - Calling validate_cvss_score Then: - No exception is raised (empty string is falsy) """ validate_cvss_score("", "Minimum CVSS Score", "8.0", "Maximum CVSS Score") def test_validate_cvss_score_with_empty_string_max(self): """ Test validate_cvss_score with empty string for max. Given: - max_cvss = "" (empty string) When: - Calling validate_cvss_score Then: - No exception is raised (empty string is falsy) """ validate_cvss_score("3.0", "Minimum CVSS Score", "", "Maximum CVSS Score") def test_validate_cvss_score_with_negative_value(self): """ Test validate_cvss_score with negative value. Given: - min_cvss = "-1.0" When: - Calling validate_cvss_score Then: - No exception is raised (function doesn't validate range, only numeric) """ with pytest.raises(DemistoException) as err: validate_cvss_score("-1.0", "Minimum CVSS Score", "5.0", "Maximum CVSS Score") assert MESSAGES["INVALID_CVSS_SCORE"].format("Minimum CVSS Score") in str(err.value) def test_validate_cvss_score_with_value_above_10(self): """ Test validate_cvss_score with value above 10. Given: - max_cvss = "15.0" When: - Calling validate_cvss_score Then: - No exception is raised (function doesn't validate range, only numeric) """ with pytest.raises(DemistoException) as err: validate_cvss_score("5.0", "Minimum CVSS Score", "15.0", "Maximum CVSS Score") assert MESSAGES["INVALID_CVSS_SCORE"].format("Maximum CVSS Score") in str(err.value) def test_validate_cvss_score_with_special_characters(self): """ Test validate_cvss_score with special characters. Given: - min_cvss = "7.5!@" When: - Calling validate_cvss_score Then: - Raises DemistoException """ with pytest.raises(DemistoException) as err: validate_cvss_score("7.5!@", "Minimum CVSS Score", "8.0", "Maximum CVSS Score") assert MESSAGES["INVALID_CVSS_SCORE"].format("Minimum CVSS Score") in str(err.value) def test_validate_cvss_score_with_whitespace(self): """ Test validate_cvss_score with whitespace in value. Given: - min_cvss = " 5.0 " When: - Calling validate_cvss_score Then: - No exception is raised (float() handles whitespace) """ validate_cvss_score(" 5.0 ", "Minimum CVSS Score", " 8.0 ", "Maximum CVSS Score") """ TEST CASES FOR COMMAND FUNCTIONS """ class TestTestModule: """Test cases for test_module function.""" def test_test_module_success(self, mock_client, requests_mock): """ Test test_module with successful API response. Given: - Mock client and successful API response. When: - Calling test_module function. Then: - Returns 'ok'. """ response = util_load_json("test_data/vulnerability_list_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=response, status_code=200) result = main_test_module(client=mock_client) assert result == "ok" def test_test_module_with_feed_enabled(self, mock_client, requests_mock, mocker): """ Test test_module with feed enabled. Given: - Mock client with feed enabled. When: - Calling test_module function. Then: - Returns 'ok' after calling fetch_indicators_command. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "feed": True} mocker.patch.object(demisto, "params", return_value=params) result = main_test_module(client=mock_client) assert result == "ok" @pytest.mark.parametrize( "status_code, error_msg", [ (400, HTTP_ERRORS[400]), (401, HTTP_ERRORS[401]), (403, HTTP_ERRORS[403]), (404, HTTP_ERRORS[404]), (500, HTTP_ERRORS[500]), ], ) def test_test_module_http_errors(self, mock_client, requests_mock, status_code, error_msg): """ Test test_module with various HTTP error responses. Given: - Mock client and HTTP error response. When: - Calling test_module function. Then: - Raises DemistoException with appropriate error message. """ requests_mock.get( f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json={"message": "error", "type": "validation_error", "errors": [{"detail": "invalid param"}]}, status_code=status_code, ) with pytest.raises(DemistoException) as err: main_test_module(client=mock_client) assert error_msg.format("validation_error", "invalid param") in str(err.value) class TestFetchIndicatorsCommand: """Test cases for fetch_indicators_command function.""" def test_fetch_indicators_success(self, mock_client, requests_mock): """ Test fetch_indicators_command with successful response. Given: - Mock client and successful API response. When: - Calling fetch_indicators_command function. Then: - Returns list of indicators and next_run dict. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") indicators_response = util_load_json("test_data/indicator_with_cve.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days", "createRelationship": True} last_run: dict = {} indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) assert len(indicators) == 1 assert indicators[0]["value"] == "CVE-YYYY-XXXX" assert indicators[0]["type"] == "CVE" assert indicators[0] == indicators_response def test_fetch_indicators_empty_response(self, mock_client, requests_mock): """ Test fetch_indicators_command with empty response. Given: - Mock client and empty API response. When: - Calling fetch_indicators_command function. Then: - Returns empty list of indicators. """ empty_response = util_load_json("test_data/vulnerability_list_empty_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=empty_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {} indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) assert len(indicators) == 0 assert "next_last_touched_after" in next_run def test_fetch_indicators_with_last_run(self, mock_client, requests_mock): """ Test fetch_indicators_command with existing last_run. Given: - Mock client and existing last_run data. When: - Calling fetch_indicators_command function. Then: - Uses last_run data for API request. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {"next_last_touched_after": "2026-01-01T00:00:00Z"} indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) assert len(indicators) == 1 assert "next_last_touched_after" in next_run def test_fetch_indicators_is_test_mode(self, mock_client, requests_mock): """ Test fetch_indicators_command in test mode. Given: - Mock client with is_test=True. When: - Calling fetch_indicators_command function. Then: - Returns empty indicators and next_run. """ list_response = util_load_json("test_data/vulnerability_list_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {} indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run, is_test=True) assert indicators == [] assert next_run == {} def test_fetch_indicators_pagination_when_page_size_reached(self, mock_client, requests_mock, mocker): """ Test fetch_indicators_command pagination logic when PAGE_SIZE vulnerabilities are returned. Given: - Mock client and API response with exactly PAGE_SIZE vulnerabilities. - Existing last_run with from=0. When: - Calling fetch_indicators_command function. Then: - Returns indicators and next_run with incremented 'from' value. - next_run should copy last_run and increment 'from' by PAGE_SIZE. - next_last_touched_after and next_last_touched_before should be set to last_touched_before. """ # Create a list response with exactly PAGE_SIZE vulnerabilities vulnerabilities_list = [{"id": i} for i in range(1, PAGE_SIZE + 1)] list_response = { "total": 500, "next": f"https://api.example.com/vulnerabilities?from={PAGE_SIZE}&size={PAGE_SIZE}", "previous": None, "size": PAGE_SIZE, "from": 0, "results": vulnerabilities_list, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) # Mock get_vulnerability for each ID for i in range(1, PAGE_SIZE + 1): detail_response = { "id": i, "cve_ids": [f"CVE-2024-{i:04d}"], "description": f"Test vulnerability {i}", "published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z", "cvssv3_score": 7.5, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/{i}", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {"from": 0, "next_last_touched_before": "2026-01-10T00:00:00Z"} # Mock arg_to_datetime to return a fixed time mocker.patch( "FeedFlashpointVulnerability.arg_to_datetime", return_value=mocker.MagicMock(strftime=lambda x: "2026-01-10T00:00:00Z"), ) indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) # Verify pagination logic (lines 564-568) assert len(indicators) == PAGE_SIZE assert next_run["from"] == 0 + PAGE_SIZE assert next_run["next_last_touched_after"] == "2026-01-10T00:00:00Z" assert next_run["next_last_touched_before"] == "2026-01-10T00:00:00Z" def test_fetch_indicators_pagination_with_existing_from_value(self, mock_client, requests_mock, mocker): """ Test fetch_indicators_command pagination when last_run already has a 'from' value. Given: - Mock client and API response with exactly PAGE_SIZE vulnerabilities. - Existing last_run with from=2000. When: - Calling fetch_indicators_command function. Then: - next_run['from'] should be incremented from 2000 to 2000 + PAGE_SIZE. """ # Create a list response with exactly PAGE_SIZE vulnerabilities vulnerabilities_list = [{"id": i} for i in range(2001, 2000 + PAGE_SIZE + 1)] list_response = { "total": 5000, "next": f"https://api.example.com/vulnerabilities?from={2000 + PAGE_SIZE}&size={PAGE_SIZE}", "previous": None, "size": PAGE_SIZE, "from": 2000, "results": vulnerabilities_list, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) # Mock get_vulnerability for each ID for i in range(2001, 2000 + PAGE_SIZE + 1): detail_response = { "id": i, "cve_ids": [f"CVE-2024-{i:04d}"], "description": f"Test vulnerability {i}", "published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z", "cvssv3_score": 7.5, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/{i}", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {"from": 2000, "next_last_touched_before": "2026-01-10T00:00:00Z"} # Mock arg_to_datetime to return a fixed time mocker.patch( "FeedFlashpointVulnerability.arg_to_datetime", return_value=mocker.MagicMock(strftime=lambda x: "2026-01-10T00:00:00Z"), ) indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) # Verify pagination logic - from should increment from 2000 to 2000 + PAGE_SIZE assert len(indicators) == PAGE_SIZE assert next_run["from"] == 2000 + PAGE_SIZE assert next_run["next_last_touched_after"] == "2026-01-10T00:00:00Z" assert next_run["next_last_touched_before"] == "2026-01-10T00:00:00Z" def test_fetch_indicators_no_pagination_when_less_than_page_size(self, mock_client, requests_mock): """ Test fetch_indicators_command when fewer than PAGE_SIZE vulnerabilities are returned. Given: - Mock client and API response with fewer than PAGE_SIZE vulnerabilities. When: - Calling fetch_indicators_command function. Then: - next_run should NOT include 'from' field (no pagination needed). - next_last_touched_after should be set to next_last_touched_before from last_run or current time. """ # Create a list response with only 10 vulnerabilities (less than PAGE_SIZE) vulnerabilities_list = [{"id": i} for i in range(1, 11)] list_response = { "total": 10, "next": None, "previous": None, "size": 10, "from": 0, "results": vulnerabilities_list, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) # Mock get_vulnerability for each ID for i in range(1, 11): detail_response = { "id": i, "cve_ids": [f"CVE-2024-{i:04d}"], "description": f"Test vulnerability {i}", "published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z", "cvssv3_score": 7.5, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/{i}", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = {"next_last_touched_before": "2026-01-10T00:00:00Z"} indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) # Verify no pagination - 'from' should not be in next_run assert len(indicators) == 10 assert "from" not in next_run assert next_run["next_last_touched_after"] == "2026-01-10T00:00:00Z" def test_fetch_indicators_timeout_after_4_minutes(self, mock_client, requests_mock, mocker): """ Test fetch_indicators_command when 4-minute timeout is exceeded. Given: - Mock client and API response with 50 vulnerabilities. - Mock datetime.now(timezone.utc) to simulate timeout after processing 10 indicators. When: - Calling fetch_indicators_command function. Then: - Exits loop early after timeout. - Returns only indicators processed before timeout (10 out of 50). - Sets pagination in next_run since len(vulnerabilities_ids) != len(indicators). """ # Create a list response with 50 vulnerabilities vulnerabilities_list = [{"id": i} for i in range(1, 51)] list_response = { "total": 50, "next": None, "previous": None, "size": 50, "from": 0, "results": vulnerabilities_list, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) # Mock get_vulnerability for each ID for i in range(1, 51): detail_response = { "id": i, "cve_ids": [f"CVE-2024-{i:04d}"], "description": f"Test vulnerability {i}", "published_at": "2026-01-01T00:00:00Z", "last_modified_at": "2026-01-01T00:00:00Z", "cvssv3_score": 7.5, } requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/{i}", json=detail_response, status_code=200) params: dict = {**BASIC_PARAMS, "first_fetch": "3 days"} last_run: dict = { "from": 0, "next_last_touched_after": "2026-01-05T00:00:00Z", "next_last_touched_before": "2026-01-10T00:00:00Z", } # Mock datetime.now(timezone.utc) to simulate timeout after 10 iterations from datetime import datetime, timedelta # Create a fixed start time (timezone-aware) start_time = datetime(2026, 1, 10, 12, 0, 0, tzinfo=UTC) call_count = {"count": 0} def mock_datetime_now(tz=None): call_count["count"] += 1 if call_count["count"] <= 1: # First call: initial current_time at line 532 return start_time elif call_count["count"] <= 11: # Calls 2-11: within timeout (10 iterations in the loop at line 549) # Return time 60 seconds after start (within 240 second limit) return start_time + timedelta(seconds=60) else: # Call 12+: exceed timeout (> 240 seconds) # Return time 250 seconds after start (exceeds 240 second limit) return start_time + timedelta(seconds=250) # Patch datetime.now to use our mock function mock_datetime_class = mocker.patch("FeedFlashpointVulnerability.datetime") mock_datetime_class.now = mock_datetime_now # Mock arg_to_datetime for fetch_params mocker.patch( "FeedFlashpointVulnerability.arg_to_datetime", return_value=mocker.MagicMock(strftime=lambda x: "2026-01-10T00:00:00Z"), ) indicators, next_run = fetch_indicators_command(client=mock_client, params=params, last_run=last_run) # Verify timeout behavior # Should process only 10 indicators before timeout (not all 50) assert len(indicators) == 10 assert len(indicators) < 50 # Didn't process all vulnerabilities # Verify pagination is set because len(vulnerabilities_ids) != len(indicators) assert "from" in next_run assert next_run["from"] == 0 + len(indicators) # 0 + 10 = 10 assert next_run["next_last_touched_after"] == "2026-01-05T00:00:00Z" # from last_run assert next_run["next_last_touched_before"] == "2026-01-10T00:00:00Z" # from last_run class TestValidateFetchIndicatorsParams: """Test cases for validate_fetch_indicators_params function.""" def test_validate_fetch_indicators_params_with_filters(self): """ Test validate_fetch_indicators_params with filter parameters. Given: - Parameters with various filters. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with all filter parameters. """ params = { "first_fetch": "3 days", "tags": "kev,zero_day", "min_cvssv3_score": "7.0", "max_cvssv3_score": "10.0", "products": "product1,product2", "vendors": "vendor1", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert "last_touched_after" in fetch_params assert "last_touched_before" in fetch_params assert fetch_params["tags"] == "kev,zero_day" assert fetch_params["min_cvssv3_score"] == "7.0" assert fetch_params["products"] == "product1,product2" def test_validate_fetch_indicators_params_with_last_run(self): """ Test validate_fetch_indicators_params with last_run data. Given: - Parameters and existing last_run. When: - Calling validate_fetch_indicators_params function. Then: - Uses last_run values for last_touched_after. """ params = {"first_fetch": "3 days"} last_run = {"next_last_touched_after": "2026-01-01T00:00:00Z", "from": 100} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["last_touched_after"] == "2026-01-01T00:00:00Z" assert fetch_params["from"] == 100 def test_validate_fetch_indicators_params_with_valid_severities(self): """ Test validate_fetch_indicators_params with valid severity values. Given: - Parameters with valid severity values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with severity parameter. """ params = { "first_fetch": "3 days", "severities": "critical,high,medium", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["severity"] == "critical,high,medium" def test_validate_fetch_indicators_params_with_invalid_severities_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid severity values in test mode. Given: - Parameters with invalid severity values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "severities": "critical,invalid_severity", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "invalid_severity" in str(err.value) assert "Severity" in str(err.value) def test_validate_fetch_indicators_params_with_valid_ref_types(self): """ Test validate_fetch_indicators_params with valid reference type values. Given: - Parameters with valid reference type values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with ref_types parameter. """ params = { "first_fetch": "3 days", "ref_types": "cve id,exploit database,metasploit url", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["ref_types"] == "cveid,exploitdb,metasploit" def test_validate_fetch_indicators_params_with_invalid_ref_types_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid reference type values in test mode. Given: - Parameters with invalid reference type values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "ref_types": "cveid,invalid_ref_type", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "invalid_ref_type" in str(err.value) assert "Reference Types" in str(err.value) def test_validate_fetch_indicators_params_with_valid_cwe_ids(self): """ Test validate_fetch_indicators_params with valid CWE ID values. Given: - Parameters with valid CWE ID values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with cwe_ids parameter. """ params = { "first_fetch": "3 days", "cwe_ids": "79,89,22", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["cwe_ids"] == "79,89,22" def test_validate_fetch_indicators_params_with_invalid_cwe_ids_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid CWE ID values in test mode. Given: - Parameters with non-integer CWE ID values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "cwe_ids": "79,not_a_number,22", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "not_a_number" in str(err.value) assert "CWE IDs" in str(err.value) def test_validate_fetch_indicators_params_with_valid_locations(self): """ Test validate_fetch_indicators_params with valid location values. Given: - Parameters with valid location values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with location parameter mapped correctly. """ params = { "first_fetch": "3 days", "locations": "remote / network access,local access required", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert "remote" in fetch_params["location"] assert "local" in fetch_params["location"] def test_validate_fetch_indicators_params_with_invalid_locations_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid location values in test mode. Given: - Parameters with invalid location values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "locations": "remote / network access,invalid_location", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "invalid_location" in str(err.value) assert "Locations" in str(err.value) def test_validate_fetch_indicators_params_with_valid_ransomware_scores(self): """ Test validate_fetch_indicators_params with valid ransomware score values. Given: - Parameters with valid ransomware score values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with ransomware_score parameter. """ params = { "first_fetch": "3 days", "ransomware_scores": "critical,high", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["ransomware_score"] == "critical,high" def test_validate_fetch_indicators_params_with_invalid_ransomware_scores_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid ransomware score values in test mode. Given: - Parameters with invalid ransomware score values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "ransomware_scores": "critical,invalid_score", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "invalid_score" in str(err.value) assert "Ransomware Scores" in str(err.value) def test_validate_fetch_indicators_params_with_valid_attack_types(self): """ Test validate_fetch_indicators_params with valid attack type values. Given: - Parameters with valid attack type values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with attack_type parameter. """ params = { "first_fetch": "3 days", "attack_types": "cryptographic,man-in-the-middle (mitm),race condition", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["attack_type"] == "crypt,mitm,race" def test_validate_fetch_indicators_params_with_invalid_attack_types_in_test_mode(self): """ Test validate_fetch_indicators_params with invalid attack type values in test mode. Given: - Parameters with invalid attack type values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with appropriate error message. """ params = { "first_fetch": "3 days", "attack_types": "crypt,invalid_attack", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) assert "invalid_attack" in str(err.value) assert "Attack Types" in str(err.value) def test_validate_fetch_indicators_params_with_multiple_invalid_params_in_test_mode(self): """ Test validate_fetch_indicators_params with multiple invalid parameters in test mode. Given: - Parameters with multiple invalid values and is_test=True. When: - Calling validate_fetch_indicators_params function. Then: - Raises DemistoException with all error messages. """ params = { "first_fetch": "3 days", "severities": "invalid_severity", "attack_types": "invalid_attack", "cwe_ids": "not_a_number", } last_run: dict = {} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params, last_run, is_test=True) error_msg = str(err.value) assert "invalid_severity" in error_msg assert "invalid_attack" in error_msg assert "not_a_number" in error_msg def test_validate_fetch_indicators_params_ignores_invalid_values_when_not_test_mode(self): """ Test validate_fetch_indicators_params ignores invalid values when not in test mode. Given: - Parameters with invalid values and is_test=False. When: - Calling validate_fetch_indicators_params function. Then: - Returns params with only valid values, no exception raised. """ params = { "first_fetch": "3 days", "severities": "critical,invalid_severity", "cwe_ids": "79,not_a_number", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run, is_test=False) assert fetch_params["severity"] == "critical" assert fetch_params["cwe_ids"] == "79" def test_validate_fetch_indicators_params_with_score_filters(self): """ Test validate_fetch_indicators_params with CVSS and EPSS score filters. Given: - Parameters with various score filters. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with all score parameters. """ params = { "first_fetch": "3 days", "min_epss_score": "0.5", "max_epss_score": "1.0", "min_cvssv2_score": "5.0", "max_cvssv2_score": "10.0", "min_cvssv3_score": "7.0", "max_cvssv3_score": "10.0", "min_cvssv4_score": "6.0", "max_cvssv4_score": "9.0", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["min_epss_score"] == "0.5" assert fetch_params["max_epss_score"] == "1.0" assert fetch_params["min_cvssv2_score"] == "5.0" assert fetch_params["max_cvssv2_score"] == "10.0" assert fetch_params["min_cvssv3_score"] == "7.0" assert fetch_params["max_cvssv3_score"] == "10.0" assert fetch_params["min_cvssv4_score"] == "6.0" assert fetch_params["max_cvssv4_score"] == "9.0" def test_validate_fetch_indicators_params_with_ref_values(self): """ Test validate_fetch_indicators_params with ref_values parameter. Given: - Parameters with ref_values. When: - Calling validate_fetch_indicators_params function. Then: - Returns dict with ref_values parameter. """ params = { "first_fetch": "3 days", "ref_values": "CVE-2024-1234,CVE-2024-5678", } last_run: dict = {} fetch_params = validate_fetch_indicators_params(params, last_run) assert fetch_params["ref_values"] == "CVE-2024-1234,CVE-2024-5678" """ TEST CASES FOR CLIENT ERROR HANDLING """ class TestClientErrorHandling: """Test cases for Client error handling.""" def test_handle_errors_400_with_json_response(self, mock_client, requests_mock): """ Test handle_errors with 400 status and JSON error response. Given: - API response with 400 status and JSON error details. When: - Calling API method. Then: - Raises DemistoException with type and detail from response. """ requests_mock.get( f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json={"type": "validation_error", "errors": [{"detail": "Invalid parameter value"}]}, status_code=400, ) with pytest.raises(DemistoException) as err: mock_client.list_vulnerabilities() assert "validation_error" in str(err.value) assert "Invalid parameter value" in str(err.value) def test_handle_errors_400_with_non_json_response(self, mock_client, requests_mock): """ Test handle_errors with 400 status and non-JSON error response. Given: - API response with 400 status and non-JSON content. When: - Calling API method. Then: - Raises DemistoException with raw text. """ requests_mock.get( f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", text="Bad Request", status_code=400, ) with pytest.raises(DemistoException) as err: mock_client.list_vulnerabilities() assert "Bad request" in str(err.value) def test_handle_errors_400_with_empty_errors_array(self, mock_client, requests_mock): """ Test handle_errors with 400 status and empty errors array. Given: - API response with 400 status and empty errors array. When: - Calling API method. Then: - Raises DemistoException without crashing. """ requests_mock.get( f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json={"type": "validation_error", "errors": [{}]}, status_code=400, ) with pytest.raises(DemistoException) as err: mock_client.list_vulnerabilities() assert "Bad request" in str(err.value) """ TEST CASES FOR MAIN FUNCTION """ class TestMain: """Test cases for main function.""" @patch("FeedFlashpointVulnerability.return_results") def test_main_test_module(self, mock_return, requests_mock, mocker): """ Test main function with test-module command. Given: - Mock environment with test-module command. When: - Calling main function. Then: - Returns 'ok' result. """ response = util_load_json("test_data/vulnerability_list_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=response, status_code=200) mocker.patch.object(demisto, "params", return_value=BASIC_PARAMS) mocker.patch.object(demisto, "command", return_value="test-module") main() assert mock_return.call_args.args[0] == "ok" @patch("FeedFlashpointVulnerability.return_results") def test_main_fetch_indicators(self, mock_return, requests_mock, mocker): """ Test main function with fetch-indicators command. Given: - Mock environment with fetch-indicators command. When: - Calling main function. Then: - Creates indicators and sets last run. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) mocker.patch.object(demisto, "params", return_value=BASIC_PARAMS) mocker.patch.object(demisto, "command", return_value="fetch-indicators") mocker.patch.object(demisto, "getLastRun", return_value={}) mock_set_last_run = mocker.patch.object(demisto, "setLastRun") mock_create_indicators = mocker.patch.object(demisto, "createIndicators") main() mock_set_last_run.assert_called_once() mock_create_indicators.assert_called_once() @pytest.mark.parametrize( "params, err_msg", [ ({}, MESSAGES["NO_PARAM_PROVIDED"].format("Server URL")), ({"url": MOCK_URL}, MESSAGES["NO_PARAM_PROVIDED"].format("API Key")), ({"url": MOCK_URL, "credentials": {"password": API_KEY}}, MESSAGES["NO_PARAM_PROVIDED"].format("Source Reliability")), ], ) def test_main_invalid_params(self, params, err_msg, mocker): """ Test main function with invalid parameters. Given: - Invalid parameters. When: - Calling main function. Then: - Returns error message. """ mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="test-module") return_error = mocker.patch.object(FeedFlashpointVulnerability, "return_error") main() assert err_msg in return_error.call_args[0][0] def test_main_not_implemented_command(self, mocker): """ Test main function with not implemented command. Given: - Not implemented command. When: - Calling main function. Then: - Returns NotImplementedError message. """ mocker.patch.object(demisto, "params", return_value=BASIC_PARAMS) mocker.patch.object(demisto, "command", return_value="unknown-command") return_error = mocker.patch.object(FeedFlashpointVulnerability, "return_error") main() assert "not implemented" in return_error.call_args[0][0].lower() """ TEST CASES FOR CLIENT CLASS """ class TestClient: """Test cases for Client class.""" def test_client_initialization(self): """ Test Client class initialization. Given: - Valid client parameters. When: - Creating Client instance. Then: - Client is created with correct attributes. """ client = Client( base_url=MOCK_URL, api_key=API_KEY, verify=True, proxy=False, ) assert client._base_url == MOCK_URL assert "Authorization" in client._headers assert f"Bearer {API_KEY}" in client._headers["Authorization"] def test_client_list_vulnerabilities(self, mock_client, requests_mock): """ Test Client.list_vulnerabilities method. Given: - Mock client and API response. When: - Calling list_vulnerabilities method. Then: - Returns API response data. """ response = util_load_json("test_data/vulnerability_list_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=response, status_code=200) result = mock_client.list_vulnerabilities(params={"size": 10}) assert result["total"] == 123456 assert len(result["results"]) == 1 def test_client_get_vulnerability(self, mock_client, requests_mock): """ Test Client.get_vulnerability method. Given: - Mock client and API response. When: - Calling get_vulnerability method. Then: - Returns vulnerability detail data. """ response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=response, status_code=200) result = mock_client.get_vulnerability(id="111111") assert result["id"] == 111111 assert result["cve_ids"] == ["CVE-YYYY-XXXX", "CVE-YYYY-ZZZZ"] def test_client_handle_errors_known_status(self, mock_client, requests_mock): """ Test Client.handle_errors with known HTTP status code. Given: - API response with 401 status. When: - Calling API method. Then: - Raises DemistoException with appropriate message. """ requests_mock.get( f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json={"message": "Unauthorized"}, status_code=401, ) with pytest.raises(DemistoException) as err: mock_client.list_vulnerabilities() assert HTTP_ERRORS[401] in str(err.value) class TestGetIndicatorsCommand: """Test cases for flashpoint_vulnerability_get_indicators_command function.""" @patch("FeedFlashpointVulnerability.return_results") def test_get_indicators_command_success(self, mock_return, requests_mock, mocker): """ Test flashpoint_vulnerability_get_indicators_command with successful response. Given: - Mock client and successful API response. When: - Calling main function with flashpoint-vulnerability-get-indicators command. Then: - Returns CommandResults with indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") expected_outputs = util_load_json("test_data/get_indicators_output.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "./test_data/get_indicators.md")) as file: hr_output = file.read() params = BASIC_PARAMS.copy() params.update({"createRelationship": "True"}) args = {"limit": "10", "first_fetch": "3 days"} mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "command", return_value="flashpoint-vulnerability-get-indicators") mocker.patch.object(demisto, "args", return_value=args) main() result = mock_return.call_args.args[0] assert result.outputs_prefix == "FlashpointVulnerabilityFeed.Indicator" assert result.outputs_key_field == "value" assert result.outputs == expected_outputs assert result.readable_output == hr_output def test_get_indicators_command_with_filters(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with filter parameters. Given: - Mock client and filter arguments. When: - Calling flashpoint_vulnerability_get_indicators_command with filters. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = { "limit": "10", "first_fetch": "3 days", "severities": "Critical,High", "min_cvssv3_score": "7.0", "max_cvssv3_score": "10.0", } result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_no_indicators_found(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with no indicators. Given: - Mock client and empty API response. When: - Calling flashpoint_vulnerability_get_indicators_command. Then: - Returns CommandResults with no indicators message. """ empty_response = util_load_json("test_data/vulnerability_list_empty_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=empty_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert MESSAGES["NO_INDICATORS_FOUND"] in result.readable_output # type: ignore[operator] def test_get_indicators_command_with_limit(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with custom limit. Given: - Mock client and custom limit argument. When: - Calling flashpoint_vulnerability_get_indicators_command with limit=5. Then: - Returns CommandResults respecting the limit. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "5", "first_fetch": "3 days"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) <= 5 # type: ignore[arg-type] def test_get_indicators_command_with_ransomware_score(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with ransomware score filter. Given: - Mock client and ransomware score filter. When: - Calling flashpoint_vulnerability_get_indicators_command with ransomware_scores. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "ransomware_scores": "Critical,High"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_attack_types(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with attack types filter. Given: - Mock client and attack types filter. When: - Calling flashpoint_vulnerability_get_indicators_command with attack_types. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "attack_types": "cryptographic,Man-In-The-Middle (Mitm)"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_products_and_vendors(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with products and vendors filters. Given: - Mock client and products/vendors filters. When: - Calling flashpoint_vulnerability_get_indicators_command with products and vendors. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "products": "Windows,Linux", "vendors": "Microsoft,RedHat"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_cwe_ids(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with CWE IDs filter. Given: - Mock client and CWE IDs filter. When: - Calling flashpoint_vulnerability_get_indicators_command with cwe_ids. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "cwe_ids": "79,89,119"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_cvss_scores(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with CVSS score filters. Given: - Mock client and CVSS score filters. When: - Calling flashpoint_vulnerability_get_indicators_command with min/max CVSS scores. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = { "limit": "10", "first_fetch": "3 days", "min_cvssv2_score": "5.0", "max_cvssv2_score": "10.0", "min_cvssv3_score": "7.0", "max_cvssv3_score": "10.0", "min_cvssv4_score": "6.0", "max_cvssv4_score": "10.0", } result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_reference_types(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with reference types filter. Given: - Mock client and reference types filter. When: - Calling flashpoint_vulnerability_get_indicators_command with ref_types. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "ref_types": "CVE ID,Exploit Database,Metasploit URL"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_locations(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with locations filter. Given: - Mock client and locations filter. When: - Calling flashpoint_vulnerability_get_indicators_command with locations. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "locations": "Remote / Network Access,Local Access Required"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_epss_scores(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with EPSS score filters. Given: - Mock client and EPSS score filters. When: - Calling flashpoint_vulnerability_get_indicators_command with min/max EPSS scores. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "min_epss_score": "0.5", "max_epss_score": "1.0"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_with_tags(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with tags filter. Given: - Mock client and tags filter. When: - Calling flashpoint_vulnerability_get_indicators_command with tags. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days", "tags": "exploit,rce,privilege-escalation"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_timeout_warning(self, mock_client, requests_mock, mocker): """ Test flashpoint_vulnerability_get_indicators_command with timeout exceeded. Given: - Mock client and multiple vulnerabilities causing timeout. When: - Calling flashpoint_vulnerability_get_indicators_command and exceeding 4 minutes. Then: - Returns CommandResults with warning and partial indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) start_time = datetime.now(UTC) timeout_time = start_time + timedelta(seconds=241) mock_datetime = mocker.patch("FeedFlashpointVulnerability.datetime") mock_datetime.now.side_effect = [start_time, timeout_time] mocker.patch("FeedFlashpointVulnerability.return_warning") params = BASIC_PARAMS.copy() args = {"limit": "10", "first_fetch": "3 days"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) def test_get_indicators_command_with_all_filters(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command with all filter parameters. Given: - Mock client and all available filter arguments. When: - Calling flashpoint_vulnerability_get_indicators_command with all filters. Then: - Returns CommandResults with filtered indicators. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() args = { "limit": "10", "first_fetch": "3 days", "ransomware_scores": "Critical,High", "attack_types": "cryptographic,Man-In-The-Middle (Mitm)", "severities": "Critical,High", "products": "Windows", "vendors": "Microsoft", "cwe_ids": "79,89", "min_cvssv3_score": "7.0", "max_cvssv3_score": "10.0", "ref_types": "CVE ID,Exploit Database", "ref_values": "CVE-2024-1234", "locations": "Remote / Network Access", "min_epss_score": "0.5", "max_epss_score": "1.0", "tags": "exploit,rce", } result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert len(result.outputs) == 1 # type: ignore[arg-type] def test_get_indicators_command_outputs_structure(self, mock_client, requests_mock): """ Test flashpoint_vulnerability_get_indicators_command output structure. Given: - Mock client and successful API response. When: - Calling flashpoint_vulnerability_get_indicators_command. Then: - Returns CommandResults with correct output structure. """ list_response = util_load_json("test_data/vulnerability_list_200.json") detail_response = util_load_json("test_data/vulnerability_detail_200.json") expected_outputs = util_load_json("test_data/get_indicators_output.json") with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "./test_data/get_indicators.md")) as file: hr_output = file.read() requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}", json=list_response, status_code=200) requests_mock.get(f"{MOCK_URL}{URL_SUFFIX['VULNERABILITIES']}/111111", json=detail_response, status_code=200) params = BASIC_PARAMS.copy() params.update({"createRelationship": "True"}) args = {"limit": "10", "first_fetch": "3 days"} result = flashpoint_vulnerability_get_indicators_command(client=mock_client, params=params, args=args) assert isinstance(result, CommandResults) assert result.outputs_prefix == "FlashpointVulnerabilityFeed.Indicator" assert result.outputs_key_field == "value" assert result.outputs == expected_outputs assert result.readable_output == hr_output class TestValidationEdgeCases: """Test cases for validation edge cases to improve code coverage.""" def test_validate_epss_score_min_below_zero(self): """ Test validate_epss_score with minimum EPSS score below 0. Given: - Minimum EPSS score value of -0.1. When: - Calling validate_epss_score function. Then: - Raises DemistoException with appropriate error message. """ with pytest.raises(DemistoException) as err: validate_epss_score(min_epss="-0.1", min_epss_label="Min EPSS Score", max_epss=None, max_epss_label="Max EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Min EPSS Score") in str(err.value) def test_validate_epss_score_min_above_one(self): """ Test validate_epss_score with minimum EPSS score above 1. Given: - Minimum EPSS score value of 1.5. When: - Calling validate_epss_score function. Then: - Raises DemistoException with appropriate error message. """ with pytest.raises(DemistoException) as err: validate_epss_score(min_epss="1.5", min_epss_label="Min EPSS Score", max_epss=None, max_epss_label="Max EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Min EPSS Score") in str(err.value) def test_validate_epss_score_max_below_zero(self): """ Test validate_epss_score with maximum EPSS score below 0. Given: - Maximum EPSS score value of -0.5. When: - Calling validate_epss_score function. Then: - Raises DemistoException with appropriate error message. """ with pytest.raises(DemistoException) as err: validate_epss_score(min_epss=None, min_epss_label="Min EPSS Score", max_epss="-0.5", max_epss_label="Max EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Max EPSS Score") in str(err.value) def test_validate_epss_score_max_above_one(self): """ Test validate_epss_score with maximum EPSS score above 1. Given: - Maximum EPSS score value of 2.0. When: - Calling validate_epss_score function. Then: - Raises DemistoException with appropriate error message. """ with pytest.raises(DemistoException) as err: validate_epss_score(min_epss=None, min_epss_label="Min EPSS Score", max_epss="2.0", max_epss_label="Max EPSS Score") assert MESSAGES["INVALID_EPSS_SCORE"].format("Max EPSS Score") in str(err.value) def test_validate_fetch_indicators_params_invalid_limit_negative(self, mock_client): """ Test validate_fetch_indicators_params with negative limit value. Given: - Command arguments with negative limit value. When: - Calling validate_fetch_indicators_params with is_command=True. Then: - Raises DemistoException with appropriate error message. """ args = {"limit": "-5", "first_fetch": "3 days"} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params=args, is_command=True) assert MESSAGES["INVALID_LIMIT_PROVIDED"].format(-5, DEFAULT_LIMIT) in str(err.value) def test_validate_fetch_indicators_params_invalid_limit_exceeds_max(self, mock_client): """ Test validate_fetch_indicators_params with limit exceeding maximum. Given: - Command arguments with limit value greater than DEFAULT_LIMIT. When: - Calling validate_fetch_indicators_params with is_command=True. Then: - Raises DemistoException with appropriate error message. """ args = {"limit": "100", "first_fetch": "3 days"} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params=args, is_command=True) assert MESSAGES["INVALID_LIMIT_PROVIDED"].format(100, DEFAULT_LIMIT) in str(err.value) def test_validate_fetch_indicators_params_invalid_from_negative(self, mock_client): """ Test validate_fetch_indicators_params with negative from value. Given: - Command arguments with negative from value. When: - Calling validate_fetch_indicators_params with is_command=True. Then: - Raises DemistoException with appropriate error message. """ args = {"limit": "10", "from": "-10", "first_fetch": "3 days"} with pytest.raises(DemistoException) as err: validate_fetch_indicators_params(params=args, is_command=True) assert MESSAGES["INVALID_FROM_PROVIDED"].format(-10) in str(err.value) def test_validate_fetch_indicators_params_valid_limit_at_boundary(self, mock_client): """ Test validate_fetch_indicators_params with limit at maximum boundary. Given: - Command arguments with limit equal to DEFAULT_LIMIT. When: - Calling validate_fetch_indicators_params with is_command=True. Then: - Returns validated parameters without error. """ args = {"limit": str(DEFAULT_LIMIT), "first_fetch": "3 days"} result = validate_fetch_indicators_params(params=args, is_command=True) assert result["size"] == DEFAULT_LIMIT def test_validate_fetch_indicators_params_valid_from_zero(self, mock_client): """ Test validate_fetch_indicators_params with from value of 0. Given: - Command arguments with from value of 0. When: - Calling validate_fetch_indicators_params with is_command=True. Then: - Returns validated parameters without error. """ args = {"limit": "10", "from": "0", "first_fetch": "3 days"} result = validate_fetch_indicators_params(params=args, is_command=True) assert result["from"] == 0 class TestRemoveNullsFromDictionary: """Test cases for improved remove_nulls_from_nested_dictionary function.""" def test_remove_nulls_simple_dict(self): """ Test remove_nulls_from_nested_dictionary with simple dictionary. Given: - Dictionary with null and empty values. When: - Calling remove_nulls_from_nested_dictionary. Then: - Removes all null and empty values. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data: dict = {"key1": "value1", "key2": None, "key3": "", "key4": [], "key5": {}, "key6": (), "key7": "value7"} remove_nulls_from_nested_dictionary(data) assert data == {"key1": "value1", "key7": "value7"} def test_remove_nulls_nested_dict(self): """ Test remove_nulls_from_nested_dictionary with nested dictionary. Given: - Dictionary with nested dictionaries containing null values. When: - Calling remove_nulls_from_nested_dictionary. Then: - Recursively removes all null and empty values from nested dicts. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = { "level1": {"level2": {"key1": "value1", "key2": None, "key3": ""}, "key4": "value4"}, "key5": None, "key6": "value6", } remove_nulls_from_nested_dictionary(data) assert data == {"level1": {"level2": {"key1": "value1"}, "key4": "value4"}, "key6": "value6"} def test_remove_nulls_nested_empty_dict(self): """ Test remove_nulls_from_nested_dictionary with nested empty dictionary. Given: - Dictionary with nested empty dictionaries. When: - Calling remove_nulls_from_nested_dictionary. Then: - Removes empty nested dictionaries after cleaning. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = {"key1": "value1", "nested": {"inner": None}, "key2": "value2"} remove_nulls_from_nested_dictionary(data) assert data == {"key1": "value1", "key2": "value2"} def test_remove_nulls_list_in_dict(self): """ Test remove_nulls_from_nested_dictionary with list inside dictionary. Given: - Dictionary containing lists with null values. When: - Calling remove_nulls_from_nested_dictionary. Then: - Removes null values from lists and empty lists from dict. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = {"key1": [1, 2, None, "", 3], "key2": [], "key3": "value3"} remove_nulls_from_nested_dictionary(data) assert data == {"key1": [1, 2, 3], "key3": "value3"} def test_remove_nulls_nested_list_with_dicts(self): """ Test remove_nulls_from_nested_dictionary with nested list containing dictionaries. Given: - Dictionary with lists containing dictionaries with null values. When: - Calling remove_nulls_from_nested_dictionary. Then: - Recursively cleans dictionaries inside lists. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = {"items": [{"name": "item1", "value": None}, {"name": "item2", "value": "val2"}, {"empty": None}]} remove_nulls_from_nested_dictionary(data) assert data == {"items": [{"name": "item1"}, {"name": "item2", "value": "val2"}]} def test_remove_nulls_deeply_nested_structure(self): """ Test remove_nulls_from_nested_dictionary with deeply nested structure. Given: - Complex nested structure with multiple levels. When: - Calling remove_nulls_from_nested_dictionary. Then: - Recursively cleans all levels. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = { "level1": { "level2": { "level3": {"key1": "value1", "key2": None, "list": [1, None, {"nested": "value", "empty": ""}]}, "empty_dict": {}, }, "key3": "", }, "top_level": "value", } remove_nulls_from_nested_dictionary(data) assert data == { "level1": {"level2": {"level3": {"key1": "value1", "list": [1, {"nested": "value"}]}}}, "top_level": "value", } def test_remove_nulls_list_with_empty_nested_structures(self): """ Test remove_nulls_from_nested_dictionary with list containing empty nested structures. Given: - List with empty dictionaries and lists. When: - Calling remove_nulls_from_nested_dictionary on a dict containing the list. Then: - Removes empty nested structures from the list. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = {"items": [{"key": "value"}, {}, [], None, "", {"nested": {"inner": None}}]} remove_nulls_from_nested_dictionary(data) assert data == {"items": [{"key": "value"}]} def test_remove_nulls_preserves_valid_values(self): """ Test remove_nulls_from_nested_dictionary preserves valid values. Given: - Dictionary with valid values including 0, False, and non-empty strings. When: - Calling remove_nulls_from_nested_dictionary. Then: - Preserves all valid values including falsy but non-null values. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data = {"zero": 0, "false": False, "string": "text", "null": None, "empty": "", "list": [0, False, "text"]} remove_nulls_from_nested_dictionary(data) assert data == {"zero": 0, "false": False, "string": "text", "list": [0, False, "text"]} def test_remove_nulls_empty_dict_input(self): """ Test remove_nulls_from_nested_dictionary with empty dictionary. Given: - Empty dictionary. When: - Calling remove_nulls_from_nested_dictionary. Then: - Returns empty dictionary unchanged. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data: dict = {} remove_nulls_from_nested_dictionary(data) assert data == {} def test_remove_nulls_all_null_values(self): """ Test remove_nulls_from_nested_dictionary with all null values. Given: - Dictionary with only null and empty values. When: - Calling remove_nulls_from_nested_dictionary. Then: - Returns empty dictionary. """ from FeedFlashpointVulnerability import remove_nulls_from_nested_dictionary data: dict = {"key1": None, "key2": "", "key3": [], "key4": {}, "key5": ()} remove_nulls_from_nested_dictionary(data) assert data == {}