FortiSandboxv2

FortiSandbox is an advanced security tool that goes beyond standard sandboxing. It combines proactive mitigation, enhanced threat detection, and in-depth reporting, using Fortinet's dynamic antivirus technology, dual-level sandboxing, and FortiGuard cloud integration to counter advanced threats. It effectively detects viruses, Advanced Persistent Threats (APTs), and malicious URLs, integrating seamlessly with existing Fortinet devices like FortiGate and FortiMail for comprehensive network protection.

Forensics & Malware Analysis · FortiSandbox

Details

IDFortiSandboxv2
ProviderFortinet
CategoryForensics & Malware Analysis
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

FortiSandbox is an advanced security tool that goes beyond standard sandboxing. It combines proactive mitigation, enhanced threat detection, and in-depth reporting, using Fortinet’s dynamic antivirus technology, dual-level sandboxing, and FortiGuard cloud integration to counter advanced threats. It effectively detects viruses, Advanced Persistent Threats (APTs), and malicious URLs, integrating seamlessly with existing Fortinet devices like FortiGate and FortiMail for comprehensive network protection.
This integration was integrated and tested with version 4.4.3 of FortiSandboxv2.

Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.

Configure FortiSandbox v2 on Cortex XSOAR

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

    Parameter Description Required
    Base URL   True
    Username   True
    Password   True
    Trust any certificate (not secure)   False
    Use system proxy settings   False
    Source Reliability Reliability of the source providing the intelligence data. True
  4. Click Test to validate the URLs, token, and connection.

Commands

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

file


Runs reputation on files.

Base Command

file

Input

Argument Name Description Required
file List of files. Supports sha256, sha1, md5. Required

Context Output

Path Type Description
File.Size Number The size of the file.
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.Extension String The file extension.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
DBotScore.Message String Optional message to show an API response. For example, “Not found”.

Command example

!file file=936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af

Context Example

{
    "DBotScore": {
        "Indicator": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af",
        "Reliability": "C - Fairly reliable",
        "Score": 1,
        "Type": "file",
        "Vendor": "FortiSandboxv2"
    },
    "File": {
        "Extension": "txt",
        "Hashes": [
            {
                "type": "SHA256",
                "value": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af"
            }
        ],
        "Name": "helloworld.txt",
        "SHA256": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af"
    }
}

Human Readable Output

Metrics reported successfully.

url


Runs reputation on URLs.

Base Command

url

Input

Argument Name Description Required
url List of URLs. Required

Context Output

Path Type Description
URL.Data String The URL.
URL.DetectionEngines String The total number of engines that checked the indicator.
URL.PositiveDetections String The number of engines that positively detected the indicator as malicious.
URL.Malicious.Vendor String The vendor reporting the URL as malicious.
URL.Malicious.Description String A description of the malicious URL.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
DBotScore.Message String Optional message to show an API response. For example, “Not found”.

Command example

!url url=www.google.com

Context Example

{
    "DBotScore": {
        "Indicator": "www.google.com",
        "Reliability": "C - Fairly reliable",
        "Score": 1,
        "Type": "url",
        "Vendor": "FortiSandboxv2"
    },
    "URL": {
        "Data": "www.google.com"
    }
}

Human Readable Output

Metrics reported successfully.

fortisandbox-submission-file-upload


Scheduled command to upload any file type to be sandboxed. The system swiftly identifies and mitigates threats in files.

Base Command

fortisandbox-submission-file-upload

Input

Argument Name Description Required
entry_id An entry ID of any file to be uploaded for analysis. Required
comment Comment field, max characters allowed: 255. Optional
process_timeout Cancel processing a submission when timeout in seconds before entering virtual machine. Optional
skip_steps Comma-separated list of steps to skip from file analysis. Do not use this parameter if no step to skip. Possible values are: anti_virus, cloud, sandbox, static_scan. Optional
archive_passwords Comma-separated list of passwords needed for extracting archived/zipped files. Non-ASCII passwords are invalid. Optional
overwrite_vm_list Comma-separated list of virtual machines to use. If this field is not set, default ones will be used. Optional
force_vm_scan Whether to force the file to be scanned in a virtual machine. Possible values are: true, false. Default is false. Optional
add_to_threat_package Specifies whether the uploaded sample should be included in the threat package, based on meeting certain malware criteria. When set to true, the system will evaluate the sample and, if it qualifies, add it to the malware package. The default setting is false, indicating that the sample will not be added unless explicitly requested. Possible values are: false, true. Default is false. Optional
record Record scan process in video if VMs are involved. Possible values are: true, false. Default is false. Optional
enable_ai Enable Deep-AI mode for this scanning. Possible values are: true, false. Default is false. Optional
get_scan_report Whether to return a PDF scan report at the end of the file analysis. Possible values are: true, false. Default is false. Optional
interval The interval between each poll in seconds. Min value is 10. Default is 30. Optional
timeout The timeout for the polling in seconds. Default is 600. Optional
sid The submission ID. Hidden argument. Optional

Context Output

Path Type Description
FortiSandbox.Submission.name String The input file name.
FortiSandbox.Submission.sid String The ID of the submission.
FortiSandbox.Submission.jid String The ID of the job.
FortiSandbox.Submission.start_ts Number Start scan time in epoch, UTC.
FortiSandbox.Submission.finish_ts Number Finish scan time in epoch, UTC.
FortiSandbox.Submission.now Number FortiSandbox’s time in epoch, UTC.
FortiSandbox.Submission.untrusted Number 0: the result can be trusted 1: since this file’s scan, scan environment has changed.
FortiSandbox.Submission.rating String The rating can be one or more of the following: Clean, Low Risk, Medium Risk, High Risk, Malicious, or Other. For archive files, the possible ratings of all files in the archive are displayed. During the file scan, the rating is displayed as N/A. If a scan times out or is terminated by the system, the file will have an Other rating.
FortiSandbox.Submission.score Number One of the following: `RISK_UNKNOWN -1`, `RISK_CLEAN 0`, `RISK_MALICIOUS 1`, `RISK_HIGH 2`, `RISK_MEDIUM 3`, `RISK_LOW 4`.
FortiSandbox.Submission.sha256 String The SHA256 of the submitted file.
FortiSandbox.Submission.sha1 String The SHA1 of the submitted file.
FortiSandbox.Submission.malware_name String Virus name if it’s a known virus.
FortiSandbox.Submission.vid Number The virus ID. Detailed information of the virus can be found at: `http://www.fortiguard.com/encyclopedia/virus/#id=virus_id` by replacing `virus_id` with the given value.
FortiSandbox.Submission.infected_os String The OS version of the FortiSandbox VM that was used to make the suspicious verdict.
FortiSandbox.Submission.detection_os String The name of the virtual machine images that scanned the file.
FortiSandbox.Submission.rating_source String One of: `AV Scan`, `Cloud Query`, `Sandboxing`, `Static Scan`, `Other`.
FortiSandbox.Submission.category String One of: `Clean`, `Unknown`, `Infector`, `Worm`, `Botnet`, `Hijack`, `Stealer`, `Backdoor`, `Injector`, `Rootkit`, `Adware`, `Dropper`, `Downloader`, `Trojan`, `Riskware`, `Grayware`, or `Attacker`.
FortiSandbox.Submission.detail_url String URL to the job overview.
FortiSandbox.Submission.download_url String The input file name encoded in Base64.
FortiSandbox.Submission.false_positive_negative Number Not false positive or false negative, 1: false positive, 2: false negative.
FortiSandbox.Submission.file_name String The name of the submitted file.
File.Size Number The size of the file.
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.Extension String The file extension.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
DBotScore.Message String Optional message to show an API response. For example, “Not found”.

Command example

!fortisandbox-submission-file-upload entry_id=418@e75b29e7-17a8-41bc-8555-ef233dd8bac9

Human Readable Output

No jobs were created yet for the submission 7048126795285831956

fortisandbox-submission-url-upload


Scheduled command to upload URLs through a text file or directly to be sandboxed individually. The system rigorously examines URLs for online security hazards.

Base Command

fortisandbox-submission-url-upload

Input

Argument Name Description Required
entry_id An entry ID of a text file to be uploaded for analysis. Each URL within file the must be separated with new lines. Optional
urls Comma-separated list of URLs to upload to scan. Optional
comment Comment field, max characters allowed: 255. Optional
process_timeout The time period to stop the URLs scan, in seconds (between 30 and 1200 seconds). Optional
depth The recursive depth in which URLs are examined. Level 0 for original URL page (between 0 and 5). Optional
overwrite_vm_list Comma-separated list of virtual machines to use. If this field is not set, default ones will be used. Optional
force_vm_scan Whether to force the file to be scanned in a virtual machine. Possible values are: true, false. Default is false. Optional
add_to_threat_package Specifies whether the uploaded sample should be included in the threat package, based on meeting certain malware criteria. When set to true, the system will evaluate the sample and, if it qualifies, add it to the malware package. The default setting is false, indicating that the sample will not be added unless explicitly requested. Possible values are: false, true. Default is false. Optional
record Record scan process in video if VMs are involved. Possible values are: true, false. Optional
enable_ai Enable Deep-AI mode for this scanning. Possible values are: true, false. Optional
get_scan_report Whether to return a PDF scan report at the end of the file analysis. Note: Generating PDF scan reports can be time-consuming, especially when analyzing multiple URLs. Possible values are: true, false. Default is false. Optional
interval The interval between each poll in seconds. Min value is 10. Default is 30. Optional
timeout The timeout for the polling in seconds. Default is 600. Optional
sid The submission ID. Hidden argument. Optional

Context Output

Path Type Description
FortiSandbox.Submission.name String The input URL/file name.
FortiSandbox.Submission.sid String The ID of the submission.
FortiSandbox.Submission.jid String The ID of the job.
FortiSandbox.Submission.start_ts Number Start scan time in epoch, UTC.
FortiSandbox.Submission.finish_ts Number Finish scan time in epoch, UTC.
FortiSandbox.Submission.now Number FortiSandbox’s time in epoch, UTC.
FortiSandbox.Submission.untrusted Number 0: the result can be trusted 1: since this file’s scan, scan environment has changed.
FortiSandbox.Submission.rating String The rating can be one or more of the following: Clean, Low Risk, Medium Risk, High Risk, Malicious, or Other. For archive files, the possible ratings of all files in the archive are displayed. During the file scan, the rating is displayed as N/A. If a scan times out or is terminated by the system, the file will have an Other rating.
FortiSandbox.Submission.score Number One of the following: `RISK_UNKNOWN -1`, `RISK_CLEAN 0`, `RISK_MALICIOUS 1`, `RISK_HIGH 2`, `RISK_MEDIUM 3`, `RISK_LOW 4`.
FortiSandbox.Submission.sha256 String The SHA256 of the submitted file.
FortiSandbox.Submission.sha1 String The SHA1 of the submitted file.
FortiSandbox.Submission.malware_name String Virus name if it’s a known virus.
FortiSandbox.Submission.vid Number The virus ID. Detailed information of the virus can be found at: `http://www.fortiguard.com/encyclopedia/virus/#id=virus_id` by replacing `virus_id` with the given value.
FortiSandbox.Submission.infected_os String The OS version of the FortiSandbox VM that was used to make the suspicious verdict.
FortiSandbox.Submission.detection_os String The name of the virtual machine images that scanned the file.
FortiSandbox.Submission.rating_source String One of: `AV Scan`, `Cloud Query`, `Sandboxing`, `Static Scan`, `Other`.
FortiSandbox.Submission.category String One of: `Clean`, `Unknown`, `Infector`, `Worm`, `Botnet`, `Hijack`, `Stealer`, `Backdoor`, `Injector`, `Rootkit`, `Adware`, `Dropper`, `Downloader`, `Trojan`, `Riskware`, `Grayware`, or `Attacker`.
FortiSandbox.Submission.detail_url String URL to the job overview.
FortiSandbox.Submission.download_url String The input URL encoded in Base64.
FortiSandbox.Submission.false_positive_negative Number Not false positive or false negative, 1: false positive, 2: false negative
FortiSandbox.Submission.file_name String The name of the submitted file.
URL.Data String The URL.
URL.DetectionEngines String The total number of engines that checked the indicator.
URL.PositiveDetections String The number of engines that positively detected the indicator as malicious.
URL.Malicious.Vendor String The vendor reporting the URL as malicious.
URL.Malicious.Description String A description of the malicious URL.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
DBotScore.Message String Optional message to show an API response. For example, “Not found”.

Command example

!fortisandbox-submission-url-upload urls=www.google.com

Human Readable Output

No jobs were created yet for the submission 7048126856924000534

fortisandbox-submission-cancel


Cancel a running job submission. Note: Jobs that are already being processed cannot be canceled, only jobs that are in the queue.

Base Command

fortisandbox-submission-cancel

Input

Argument Name Description Required
id The submission ID. Required

Context Output

There is no context output for this command.

Command example

!fortisandbox-submission-cancel id=7047969418633282747

Human Readable Output

The cancellation of the submission 7047969418633282747 was successfully sent

fortisandbox-submission-job-verdict


Get the verdict of the provided job.

Base Command

fortisandbox-submission-job-verdict

Input

Argument Name Description Required
id The job ID. Use !forisandbox-submission-job-list to fetch a list of job IDs. Required

Context Output

Path Type Description
FortiSandbox.Submission.name String The input URL/file name.
FortiSandbox.Submission.start_ts Number Start scan time in epoch, UTC.
FortiSandbox.Submission.finish_ts Number Finish scan time in epoch, UTC.
FortiSandbox.Submission.now Number FortiSandbox’s time in epoch, UTC.
FortiSandbox.Submission.behavior_info Number 0: There is no analytic report for this URL. 1: There is an analytic report for this URL.
FortiSandbox.Submission.category String One of: `Clean`, `Unknown`, `Infector`, `Worm`, `Botnet`, `Hijack`, `Stealer`, `Backdoor`, `Injector`, `Rootkit`, `Adware`, `Dropper`, `Downloader`, `Trojan`, `Riskware`, `Grayware`, or `Attacker`.
FortiSandbox.Submission.detection_os String The name of the virtual machine images that scanned the file.
FortiSandbox.Submission.false_positive_negative Number Not false positive or false negative, 1: false positive, 2: false negative.
FortiSandbox.Submission.infected_os String The OS version of the FortiSandbox VM that was used to make the suspicious verdict.
FortiSandbox.Submission.malware_name String Virus name if it’s a known virus.
FortiSandbox.Submission.rating String The rating can be one or more of the following: Clean, Low Risk, Medium Risk, High Risk, Malicious, or Other. For archive files, the possible ratings of all files in the archive are displayed. During the file scan, the rating is displayed as N/A. If a scan times out or is terminated by the system, the file will have an Other rating.
FortiSandbox.Submission.rating_source String One of: `AV Scan`, `Cloud Query`, `Sandboxing`, `Static Scan`, `Other`.
FortiSandbox.Submission.score Number One of the following: `RISK_UNKNOWN -1`, `RISK_CLEAN 0`, `RISK_MALICIOUS 1`, `RISK_HIGH 2`, `RISK_MEDIUM 3`, `RISK_LOW 4`.
FortiSandbox.Submission.untrusted Number 0: the result can be trusted 1: since this files’ scan, scan environment has changed.
FortiSandbox.Submission.vid Number The virus ID. Detailed information of the virus can be found at: `http://www.fortiguard.com/encyclopedia/virus/#id=virus_id` by replacing `virus_id` with the given value.
FortiSandbox.Submission.detail_url String URL to the job overview.
FortiSandbox.Submission.download_url String The input URL/file name encoded in Base64.
FortiSandbox.Submission.jid String The ID of the job.
FortiSandbox.Submission.sha1 String The SHA1 hash of the file.
FortiSandbox.Submission.sha256 String The SHA256 hash of the file.
File.Size Number The size of the file.
File.MD5 String The MD5 hash of the file.
File.SHA1 String The SHA1 hash of the file.
File.SHA256 String The SHA256 hash of the file.
File.Name String The name of the file.
File.SSDeep String The SSDeep hash of the file.
File.EntryID String The entry ID of the file.
File.Info String File information.
File.Type String The file type.
File.Extension String The file extension.
URL.Data String The URL.
URL.DetectionEngines String The total number of engines that checked the indicator.
URL.PositiveDetections String The number of engines that positively detected the indicator as malicious.
URL.Malicious.Vendor String The vendor reporting the URL as malicious.
URL.Malicious.Description String A description of the malicious URL.
DBotScore.Indicator String The indicator that was tested.
DBotScore.Type String The indicator type.
DBotScore.Vendor String The vendor used to calculate the score.
DBotScore.Score Number The actual score.
DBotScore.Reliability String Reliability of the source providing the intelligence data.
DBotScore.Message String Optional message to show an API response. For example, “Not found”.

Command example

!fortisandbox-submission-job-verdict id=7047959279104315513

Context Example

{
    "DBotScore": {
        "Indicator": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af",
        "Message": "https://0.0.0.0/job-detail/?sid=7047959252528592869&jid=7047959279104315513&req_type=file-csearch",
        "Reliability": "C - Fairly reliable",
        "Score": 1,
        "Type": "file",
        "Vendor": "FortiSandboxv2"
    },
    "File": {
        "Extension": "txt",
        "Hashes": [
            {
                "type": "SHA1",
                "value": "6adfb183a4a2c94a2f92dab5ade762a47889a5a1"
            },
            {
                "type": "SHA256",
                "value": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af"
            }
        ],
        "Name": "helloworld.txt",
        "SHA1": "6adfb183a4a2c94a2f92dab5ade762a47889a5a1",
        "SHA256": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af"
    },
    "FortiSandbox": {
        "Submission": {
            "category": "NotApplicable",
            "detail_url": "https://0.0.0.0/job-detail/?sid=7047959252528592869&jid=7047959279104315513&req_type=file-csearch",
            "detection_os": "",
            "download_url": "aGVsbG93b3JsZC50eHQK",
            "false_positive_negative": 0,
            "file_name": "helloworld.txt",
            "finish_ts": 1710245223,
            "ftype": "txt",
            "infected_os": "",
            "jid": "7047959279104315513",
            "malware_name": "N/A",
            "name": "helloworld.txt\n",
            "now": 1710254987,
            "rating": "Clean",
            "rating_source": "Static Scan Engine",
            "score": 0,
            "sha1": "6adfb183a4a2c94a2f92dab5ade762a47889a5a1",
            "sha256": "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af",
            "start_ts": 1710245223,
            "untrusted": 1,
            "vid": 0
        }
    }
}

Human Readable Output

The verdict for the job 7047959279104315513

Jid Name Start Ts Finish Ts Category Malware Name Rating Detail Url
7047959279104315513 helloworld.txt
1710245223 1710245223 NotApplicable N/A Clean https://0.0.0.0/job-detail/?sid=7047959252528592869&jid=7047959279104315513&req_type=file-csearch

fortisandbox-submission-job-list


Get a list of jobs that were created from a submission.

Base Command

fortisandbox-submission-job-list

Input

Argument Name Description Required
id The submission ID. Use !fortisandbox-submission-file-upload or !fortisandbox-submission-url-upload to create a submission. Required

Context Output

Path Type Description
FortiSandbox.Submission.sid String The ID of the submission.
FortiSandbox.Submission.jid String The ID of the job.

Command example

!fortisandbox-submission-job-list id=7047969418633282747

Context Example

{
    "FortiSandbox": {
        "Submission": {
            "jid": "7047969436160637757",
            "sid": "7047969418633282747"
        }
    }
}

Human Readable Output

The submission 7047969418633282747 job IDs

Jid
7047969436160637757

fortisandbox-submission-job-report


Get a PDF report of the provided submission.

Base Command

fortisandbox-submission-job-report

Input

Argument Name Description Required
identifier The job ID or SHA256 of the scanned file or URL. Required

Context Output

Path Type Description
InfoFile.Name String File name.
InfoFile.EntryID String The entry ID of the report.
InfoFile.Size Number File size.
InfoFile.Type String File type “pdf”.
InfoFile.Info String Basic information of the file.

Command example

!fortisandbox-submission-job-report identifier=7047959279104315513

Context Example

{
    "InfoFile": {
        "EntryID": "449@e75b29e7-17a8-41bc-8555-ef233dd8bac9",
        "Extension": "pdf",
        "Info": "application/pdf",
        "Name": "7047959279104315513.pdf",
        "Size": 2605722,
        "Type": "PDF document, version 1.4"
    }
}

Human Readable Output

Breaking changes from the previous version of this integration - FortiSandbox v2

Commands

The following commands were removed in this version

  • fortisandbox-simple-file-rating-sha256 - this command was replaced by file.
  • fortisandbox-simple-file-rating-sha1 - this command was replaced by file.
  • fortisandbox-url-rating - this command was replaced by url.
  • fortisandbox-get-file-verdict-detailed - this command was replaced by file.
  • fortisandbox-upload-file - this command was replaced by fortisandbox-submission-upload-file.
  • fortisandbox-query-job-verdict - this command was replaced by fortisandbox-submission-job-verdict.
  • fortisandbox-jobid-from-submission - this command was replaced by fortisandbox-submission-job-list.
  • fortisandbox-get-pdf-report - this command was replaced by fortisandbox-submission-job-report.
  • fortisandbox-upload-urls - this command was replaced by fortisandbox-submission-upload-url.

Configuration parameters

  • base_url — Base URL (required)
  • credentials — Username (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • integration_reliability — Source Reliability (required)

Commands (8)

  • file

    Runs reputation on files.

  • fortisandbox-submission-cancel

    Cancel a running job submission. Note: Jobs that are already being processed cannot be canceled, only jobs that are in the queue.

  • fortisandbox-submission-file-upload

    Scheduled command to upload any file type to be sandboxed. The system swiftly identifies and mitigates threats in files.

  • fortisandbox-submission-job-list

    Get a list of jobs that were created from a submission.

  • fortisandbox-submission-job-report

    Get a PDF report of the provided submission.

  • fortisandbox-submission-job-verdict

    Get the verdict of the provided job.

  • fortisandbox-submission-url-upload

    Scheduled command to upload URLs through a text file or directly to be sandboxed individually. The system rigorously examines URLs for online security hazards.

  • url

    Runs reputation on URLs.

import json
import os
import unittest.mock
from typing import Any

import CommonServerPython
import FortiSandboxv2
import pytest
from FortiSandboxv2 import is_url

TEST_DATA = "test_data"
BASE_URL = "https://www.example.com"
API_URL = CommonServerPython.urljoin(BASE_URL, "jsonrpc/")

""" Utils """


def load_mock_response(file_name: str) -> dict[str, Any]:
    """Load mock file that simulates an API response.

    Args:
        file_name (str): Name of the mock response JSON file to return.
    Returns:
        str: Mock file content.
    """
    file_path = os.path.join(TEST_DATA, file_name)

    with open(file_path, encoding="utf-8") as mock_file:
        return json.loads(mock_file.read())


""" Fixtures """


@pytest.fixture()
def mock_client() -> FortiSandboxv2.Client:
    """
    Establish a mock connection to the client with a username and password.

    Returns:
        Client: Mock connection to client.
    """
    return FortiSandboxv2.Client(
        base_url=BASE_URL,
        username="Pokemon",
        password="Pikachu",
    )


""" Helper Tests """


def test_bool_to_int():
    """Test the bool_to_int function.

    Ensure:
    - False is converted to 0.
    - True is converted to 1.
    """
    assert FortiSandboxv2.bool_to_int(False) == 0
    assert FortiSandboxv2.bool_to_int(True) == 1


def test_bool_to_str():
    """Test the bool_to_str function.

    Ensure:
    - False is converted to "0".
    - True is converted to "1".
    """
    assert FortiSandboxv2.bool_to_str(False) == "0"
    assert FortiSandboxv2.bool_to_str(True) == "1"


@pytest.mark.parametrize(
    "indicator,indicator_type,score,rating,detail_url",
    [
        ("pikachu.exe", CommonServerPython.DBotScoreType.FILE, -1, None, "hello"),
        ("pikachu.exe", CommonServerPython.DBotScoreType.FILE, None, "Unknown", None),
        ("pikachu.exe", CommonServerPython.DBotScoreType.FILE, None, ["Unknown"], None),
        ("pikachu.exe", CommonServerPython.DBotScoreType.FILE, None, None, "hello"),
        ("pikachu.com", CommonServerPython.DBotScoreType.URL, -1, None, "hello"),
    ],
)
def test_build_dbot_score(
    indicator: str,
    indicator_type: str,
    score: int | None,
    rating: list[str] | str | None,
    detail_url: str | None,
):
    """Test the build_dbot_score function.

    Ensure:
    - The indicator is set to the expected indicator value.
    - The indicator type is set to the expected indicator type.
    - The score is set to Common.DBotScore.NONE.
    - The reliability is set to Common.DBotScoreReliability.C.
    - The message is set to the detail URL.

    Args:
        indicator (str): The indicator value.
        indicator_type (str): The type of the indicator.
        score (int | None): The score value.
        rating (list[str] | str | None): The rating value.
        detail_url (str | None): The detail URL.
    """
    dbot_score = FortiSandboxv2.build_dbot_score(
        indicator=indicator,
        indicator_type=indicator_type,
        score=score,
        rating=rating,
        detail_url=detail_url,
    )

    assert dbot_score.indicator == indicator
    assert dbot_score.indicator_type == indicator_type
    assert dbot_score.score == CommonServerPython.Common.DBotScore.NONE
    assert dbot_score.reliability == CommonServerPython.DBotScoreReliability.C
    assert dbot_score.message == detail_url


def test_build_relationship():
    """Test the build_relationship function.

    Ensure:
    - The entity is set to the expected entity value.
    """
    entity_type = CommonServerPython.FeedIndicatorType.File
    entity = "pikachu.exe"
    malware = "pikachu"

    output = FortiSandboxv2.build_relationship(
        entity_type=entity_type,
        entity=entity,
        malware=malware,
    )
    expected_result = CommonServerPython.EntityRelationship(
        name=CommonServerPython.EntityRelationship.Relationships.INDICATOR_OF,
        entity_a=entity,
        entity_a_type=entity_type,
        entity_b=malware,
        entity_b_type=CommonServerPython.FeedIndicatorType.Malware,
        reverse_name=CommonServerPython.EntityRelationship.Relationships.INDICATED_BY,
        brand="FortiSandbox",
        source_reliability=CommonServerPython.DBotScoreReliability.C,
    )
    assert output.to_context() == expected_result.to_context()


@pytest.mark.parametrize(
    "data,url,file_hash,expected_result",
    [
        (
            {
                "score": 3,
                "malware_name": "thunderbolt",
                "detail_url": "https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=url-csearch",
                "download_url": "d3d3LnBva2Vtb24uY29t",
                "category": "pokemon",
            },
            None,
            None,
            CommonServerPython.Common.URL(
                dbot_score=CommonServerPython.Common.DBotScore(
                    indicator="www.pokemon.com",
                    indicator_type=CommonServerPython.DBotScoreType.URL,
                    score=CommonServerPython.Common.DBotScore.SUSPICIOUS,
                    reliability=FortiSandboxv2.RELIABILITY,
                    message="https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=url-csearch",
                ),
                url="www.pokemon.com",
                category="pokemon",
                malware_family=["thunderbolt"],
                relationships=[
                    CommonServerPython.EntityRelationship(
                        name=CommonServerPython.EntityRelationship.Relationships.INDICATOR_OF,
                        entity_a="www.pokemon.com",
                        entity_a_type=CommonServerPython.FeedIndicatorType.URL,
                        entity_b="thunderbolt",
                        entity_b_type=CommonServerPython.FeedIndicatorType.Malware,
                        reverse_name=CommonServerPython.EntityRelationship.Relationships.INDICATED_BY,
                        brand="FortiSandbox",
                        source_reliability=FortiSandboxv2.RELIABILITY,
                    )
                ],
            ),
        ),
        (
            {
                "rating": "High Risk",
                "malware_name": "N/A",
                "detail_url": "https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=url-csearch",
                "url": "www.pokemon.com",
                "category": "NotApplicable",
            },
            None,
            None,
            CommonServerPython.Common.URL(
                dbot_score=CommonServerPython.Common.DBotScore(
                    indicator="www.pokemon.com",
                    indicator_type=CommonServerPython.DBotScoreType.URL,
                    score=CommonServerPython.Common.DBotScore.BAD,
                    reliability=FortiSandboxv2.RELIABILITY,
                    message="https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=url-csearch",
                ),
                url="www.pokemon.com",
            ),
        ),
        (
            {
                "rating": "High Risk",
            },
            "www.pokemon.com",
            None,
            CommonServerPython.Common.URL(
                dbot_score=CommonServerPython.Common.DBotScore(
                    indicator="www.pokemon.com",
                    indicator_type=CommonServerPython.DBotScoreType.URL,
                    score=CommonServerPython.Common.DBotScore.BAD,
                    reliability=FortiSandboxv2.RELIABILITY,
                ),
                url="www.pokemon.com",
            ),
        ),
        (
            {
                "score": 3,
                "malware_name": "thunderbolt",
                "detail_url": "https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=file-csearch",
                "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
                "sha1": "0000000000000000000000000000000000000000",
                "file_name": "pikachu.exe",
            },
            None,
            None,
            CommonServerPython.Common.File(
                dbot_score=CommonServerPython.Common.DBotScore(
                    indicator="0000000000000000000000000000000000000000000000000000000000000000",
                    indicator_type=CommonServerPython.DBotScoreType.FILE,
                    score=CommonServerPython.Common.DBotScore.SUSPICIOUS,
                    reliability=FortiSandboxv2.RELIABILITY,
                    message="https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=file-csearch",
                ),
                name="pikachu.exe",
                extension="exe",
                malware_family=["thunderbolt"],
                sha1="0000000000000000000000000000000000000000",
                sha256="0000000000000000000000000000000000000000000000000000000000000000",
                relationships=[
                    CommonServerPython.EntityRelationship(
                        name=CommonServerPython.EntityRelationship.Relationships.INDICATOR_OF,
                        entity_a="0000000000000000000000000000000000000000000000000000000000000000",
                        entity_a_type=CommonServerPython.FeedIndicatorType.File,
                        entity_b="thunderbolt",
                        entity_b_type=CommonServerPython.FeedIndicatorType.Malware,
                        reverse_name=CommonServerPython.EntityRelationship.Relationships.INDICATED_BY,
                        brand="FortiSandbox",
                        source_reliability=FortiSandboxv2.RELIABILITY,
                    )
                ],
            ),
        ),
        (
            {
                "rating": ["High Risk"],
                "malware_name": ["thunderbolt", "N/A"],
                "detail_url": "https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=file-csearch",
            },
            None,
            "0000000000000000000000000000000000000000000000000000000000000000",
            CommonServerPython.Common.File(
                dbot_score=CommonServerPython.Common.DBotScore(
                    indicator="0000000000000000000000000000000000000000000000000000000000000000",
                    indicator_type=CommonServerPython.DBotScoreType.FILE,
                    score=CommonServerPython.Common.DBotScore.BAD,
                    reliability=FortiSandboxv2.RELIABILITY,
                    message="https://0.0.0.0/job-detail/?sid=0&jid=0&req_type=file-csearch",
                ),
                malware_family=["thunderbolt"],
                sha256="0000000000000000000000000000000000000000000000000000000000000000",
                relationships=[
                    CommonServerPython.EntityRelationship(
                        name=CommonServerPython.EntityRelationship.Relationships.INDICATOR_OF,
                        entity_a="0000000000000000000000000000000000000000000000000000000000000000",
                        entity_a_type=CommonServerPython.FeedIndicatorType.File,
                        entity_b="thunderbolt",
                        entity_b_type=CommonServerPython.FeedIndicatorType.Malware,
                        reverse_name=CommonServerPython.EntityRelationship.Relationships.INDICATED_BY,
                        brand="FortiSandbox",
                        source_reliability=FortiSandboxv2.RELIABILITY,
                    )
                ],
            ),
        ),
    ],
)
def test_build_indicator(
    data: dict[str, Any],
    url: str | None,
    file_hash: str | None,
    expected_result: CommonServerPython.Common.Indicator,
):
    """Test the build_indicator function.

    Ensure:
    - The indicator is set to the expected indicator value.

    Args:
        data (dict[str, Any]): The data to build the indicator from.
        url (str | None): The URL to build the indicator from.
        file_hash (str | None): The file hash to build the indicator from.
        expected_result (CommonServerPython.Common.Indicator): The expected indicator.
    """
    indicator = FortiSandboxv2.build_indicator(data, url, file_hash)
    assert indicator.to_context() == expected_result.to_context()


@pytest.mark.parametrize(
    "args,expected_result",
    [
        (
            {"urls": ["https://pikachu.com", "www.pokemon.com"]},
            ("dXJsc19mb3JfdXBsb2FkXzE2MDk0NTkyMDAudHh0", "aHR0cHM6Ly9waWthY2h1LmNvbQp3d3cucG9rZW1vbi5jb20="),
        ),
        (
            {"entry_id": "123"},
            ("cGlrYWNodS5leGU=", "aHR0cHM6Ly9waWthY2h1LmNvbQp3d3cucG9rZW1vbi5jb20="),
        ),
    ],
)
@unittest.mock.patch("FortiSandboxv2.time.time", return_value=1609459200)
@unittest.mock.patch("FortiSandboxv2.demisto.getFilePath", return_value={"path": "/path", "name": "pikachu.exe"})
def test_prepare_submission_content(
    mocked_time,
    mocked_get_file_path,
    args: dict[str, Any],
    expected_result: tuple[str, str],
):
    """Test the prepare_submission_content function.

    Ensure:
    - The file name and content is set to the expected file name and content.

    Args:
        mocked_time (MagicMock): A mock for the `time.time` function to return a fixed timestamp, ensuring
            the generated file name's timestamp component is predictable for the test.
        mocked_get_file_path (MagicMock): A mock for the `demisto.getFilePath` function to simulate retrieving
            the file path and name when an `entry_id` is provided, allowing for
            testing of the file upload functionality without accessing the file system.
        args (dict[str, Any]): The arguments to pass to the function.
        expected_result (tuple[str, str]): The expected result.
    """
    mocked_open = unittest.mock.mock_open(read_data="https://pikachu.com\nwww.pokemon.com")

    with unittest.mock.patch("builtins.open", mocked_open):
        file_name, content = FortiSandboxv2.prepare_submission_content(args)

    assert (file_name, content) == expected_result


@pytest.mark.parametrize(
    "args",
    [
        ({}),
        ({"entry_id": "123", "urls": ["https://pikachu.com"]}),
    ],
)
def test_prepare_submission_content_error(args: dict[str, Any]):
    """Test the prepare_submission_content function with an error.

    Ensure:
    - A DemistoException is raised.

    Args:
        args (dict[str, Any]): The arguments to pass to the function.
    """
    with pytest.raises(CommonServerPython.DemistoException):
        FortiSandboxv2.prepare_submission_content(args)


@pytest.mark.parametrize(
    "args,integration_context,file_names,expected_context_list,expected_poll_result",
    [
        (
            {"sid": "123"},
            {},
            [
                "job_list_empty.json",
            ],
            [
                {
                    "total_jids": 0,
                    "fetched_jids": [],
                    "remaining_jids": [],
                    "jid_to_raw_response": {},
                }
            ],
            CommonServerPython.PollResult(
                response=None,
                continue_to_poll=True,
                args_for_next_run={"sid": "123"},
                partial_result=CommonServerPython.CommandResults(
                    readable_output="## No jobs were created yet for the submission 123."
                ),
            ),
        ),
        (
            {"sid": "123"},
            {
                "total_jids": 3,
                "fetched_jids": ["000"],
                "remaining_jids": ["111"],
                "jid_to_raw_response": {
                    "000": load_mock_response("job_verdict.json"),
                },
            },
            [
                "job_list_done.json",
                "job_verdict.json",
                "job_verdict.json",
            ],
            [
                {
                    "total_jids": 3,
                    "fetched_jids": ["111", "222", "000"],
                    "remaining_jids": [],
                },
                {},
            ],
            CommonServerPython.PollResult(
                response=[
                    CommonServerPython.CommandResults(
                        indicator=CommonServerPython.Common.URL(
                            dbot_score=CommonServerPython.Common.DBotScore(
                                indicator="www.pokemon.com",
                                indicator_type=CommonServerPython.DBotScoreType.URL,
                                score=CommonServerPython.Common.DBotScore.GOOD,
                                reliability=FortiSandboxv2.RELIABILITY,
                                message="https://pikachu/job-detail/?sid=456&jid=123&req_type=url-csearch",
                            ),
                            url="www.pokemon.com",
                        ),
                    ),
                    CommonServerPython.CommandResults(
                        indicator=CommonServerPython.Common.URL(
                            dbot_score=CommonServerPython.Common.DBotScore(
                                indicator="www.pokemon.com",
                                indicator_type=CommonServerPython.DBotScoreType.URL,
                                score=CommonServerPython.Common.DBotScore.GOOD,
                                reliability=FortiSandboxv2.RELIABILITY,
                                message="https://pikachu/job-detail/?sid=456&jid=123&req_type=url-csearch",
                            ),
                            url="www.pokemon.com",
                        ),
                    ),
                    CommonServerPython.CommandResults(
                        indicator=CommonServerPython.Common.URL(
                            dbot_score=CommonServerPython.Common.DBotScore(
                                indicator="www.pokemon.com",
                                indicator_type=CommonServerPython.DBotScoreType.URL,
                                score=CommonServerPython.Common.DBotScore.GOOD,
                                reliability=FortiSandboxv2.RELIABILITY,
                                message="https://pikachu/job-detail/?sid=456&jid=123&req_type=url-csearch",
                            ),
                            url="www.pokemon.com",
                        ),
                    ),
                ],
                continue_to_poll=False,
            ),
        ),
    ],
)
def test_poll_job_submissions(
    requests_mock,
    mock_client: FortiSandboxv2.Client,
    args: dict[str, Any],
    integration_context: dict[str, Any],
    file_names: list[str],
    expected_context_list: list[dict[str, Any]],
    expected_poll_result: CommonServerPython.PollResult,
):
    """Test the poll_job_submissions function.

    Ensure:
    - The integration context is set to the expected integration context.
    - The poll result is set to the expected poll result.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client.
        args (dict[str, Any]): The arguments to pass to the function.
        integration_context (dict[str, Any]): The integration context to patch.
        file_names (list[str]): The file names for the mocked responses.
        expected_context_list (list[dict[str, Any]]): A list of the expected integration context.
        expected_poll_result (CommonServerPython.PollResult): The expected poll result.
    """
    requests_mock.post(
        API_URL,
        [{"json": load_mock_response(file_name)} for file_name in file_names],
    )

    with (
        unittest.mock.patch("FortiSandboxv2.get_integration_context", return_value=integration_context),
        unittest.mock.patch("FortiSandboxv2.set_integration_context") as mock_set_integration_context,
    ):
        poll_result = FortiSandboxv2.poll_job_submissions(client=mock_client, args=args)

        for call_args, expected_context in zip(mock_set_integration_context.call_args_list, expected_context_list):
            args = call_args.args[0]
            assert set(args.get("fetched_jids", [])) == set(expected_context.get("fetched_jids", []))
            assert set(args.get("remaining_jids", [])) == set(expected_context.get("remaining_jids", []))
            assert args.get("total_jids") == expected_context.get("total_jids")

    assert poll_result.continue_to_poll == expected_poll_result.continue_to_poll
    assert poll_result.args_for_next_run == expected_poll_result.args_for_next_run

    if poll_result.response or expected_poll_result.response:
        for actual_response, expected_response in zip(poll_result.response, expected_poll_result.response):
            assert actual_response.indicator.to_context() == expected_response.indicator.to_context()

    if poll_result.partial_result or expected_poll_result.partial_result:
        assert poll_result.partial_result.to_context() == expected_poll_result.partial_result.to_context()


""" Command Tests """


def test_test_module(requests_mock, mock_client: FortiSandboxv2.Client):
    """Test the test_module function.

    Ensure:
    - The result is set to "ok".

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client.
    """
    requests_mock.post(API_URL, json={"result": {"status": {"code": 0}}})
    result = FortiSandboxv2.test_module(client=mock_client)
    assert result == "ok"


@pytest.mark.parametrize(
    "file_name,expected_outputs",
    [
        (
            "url_rating_known.json",
            [
                CommonServerPython.Common.URL(
                    dbot_score=CommonServerPython.Common.DBotScore(
                        indicator="https://www.pokemon.com",
                        indicator_type=CommonServerPython.DBotScoreType.URL,
                        score=CommonServerPython.Common.DBotScore.GOOD,
                        reliability=FortiSandboxv2.RELIABILITY,
                        message="",
                    ),
                    url="https://www.pokemon.com",
                ),
                CommonServerPython.Common.URL(
                    dbot_score=CommonServerPython.Common.DBotScore(
                        indicator="http://pikachu.com",
                        indicator_type=CommonServerPython.DBotScoreType.URL,
                        score=CommonServerPython.Common.DBotScore.BAD,
                        reliability=FortiSandboxv2.RELIABILITY,
                        message="",
                    ),
                    url="http://pikachu.com",
                ),
            ],
        ),
        (
            "url_rating_unknown.json",
            [
                CommonServerPython.create_indicator_result_with_dbotscore_unknown(
                    indicator="https://www.charizard.com",
                    indicator_type=CommonServerPython.DBotScoreType.URL,
                    reliability=FortiSandboxv2.RELIABILITY,
                ).indicator,
                CommonServerPython.create_indicator_result_with_dbotscore_unknown(
                    indicator="http://squirtle.com",
                    indicator_type=CommonServerPython.DBotScoreType.URL,
                    reliability=FortiSandboxv2.RELIABILITY,
                ).indicator,
            ],
        ),
    ],
)
def test_url_command(
    requests_mock,
    mock_client: FortiSandboxv2.Client,
    file_name: str,
    expected_outputs: list[CommonServerPython.Common.Indicator],
):
    """Test the url_command function for generic reputation.

    Ensure:
    - The indicator is set to the expected indicator value.
    - The raw response is set to the expected raw response.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
        file_name (str): The file name for the mocked response.
        expected_outputs (list[CommonServerPython.Common.Indicator]): expected output for assertion.
    """
    mock_response = load_mock_response(file_name)
    requests_mock.post(API_URL, json=mock_response)

    command_results = FortiSandboxv2.url_command(mock_client, {"url": "www.pokemon.com"})

    for command_result, expected_output in zip(command_results, expected_outputs):
        assert command_result.indicator.to_context() == expected_output.to_context()
        assert command_result.raw_response == mock_response


@pytest.mark.parametrize(
    "file_name,expected_outputs",
    [
        (
            "file_verdict_known.json",
            [
                CommonServerPython.Common.File(
                    dbot_score=CommonServerPython.Common.DBotScore(
                        indicator="0000000000000000000000000000000000000000000000000000000000000000",
                        indicator_type=CommonServerPython.DBotScoreType.FILE,
                        score=CommonServerPython.Common.DBotScore.BAD,
                        reliability=FortiSandboxv2.RELIABILITY,
                        message="",
                    ),
                    sha256="0000000000000000000000000000000000000000000000000000000000000000",
                    malware_family=["pokemon"],
                    name="pikachu.exe",
                    extension="exe",
                    relationships=[
                        CommonServerPython.EntityRelationship(
                            name=CommonServerPython.EntityRelationship.Relationships.INDICATOR_OF,
                            entity_a="0000000000000000000000000000000000000000000000000000000000000000",
                            entity_a_type=CommonServerPython.FeedIndicatorType.File,
                            entity_b="pokemon",
                            entity_b_type=CommonServerPython.FeedIndicatorType.Malware,
                            reverse_name=CommonServerPython.EntityRelationship.Relationships.INDICATED_BY,
                            brand="FortiSandbox",
                            source_reliability=FortiSandboxv2.RELIABILITY,
                        )
                    ],
                ),
            ],
        ),
        (
            "file_verdict_unknown.json",
            [
                CommonServerPython.create_indicator_result_with_dbotscore_unknown(
                    indicator="0000000000000000000000000000000000000000000000000000000000000000",
                    indicator_type=CommonServerPython.DBotScoreType.FILE,
                    reliability=FortiSandboxv2.RELIABILITY,
                ).indicator,
            ],
        ),
    ],
)
def test_file_command(
    requests_mock,
    mock_client: FortiSandboxv2.Client,
    file_name: str,
    expected_outputs: list[CommonServerPython.Common.Indicator],
):
    """Test the file_command function for generic reputation.

    Ensure:
    - The indicator is set to the expected indicator value.
    - The raw response is set to the expected raw response.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
        file_name (str | None): The file name for the mocked response.
        expected_outputs (list[CommonServerPython.Common.Indicator]): expected output for assertion.
    """
    sha256 = "0000000000000000000000000000000000000000000000000000000000000000"
    mock_response = load_mock_response(file_name)
    requests_mock.post(API_URL, json=mock_response)

    command_results = FortiSandboxv2.file_command(mock_client, {"file": sha256})

    for command_result, expected_output in zip(command_results, expected_outputs):
        assert command_result.indicator.to_context() == expected_output.to_context()
        assert command_result.raw_response == mock_response


def test_submission_file_upload_command_error(mock_client: FortiSandboxv2.Client):
    """Test the submission_file_upload_command function with an error.

    Ensure:
    - A DemistoException is raised.

    Args:
        mock_client (FortiSandboxv2.Client): Mocked client.
    """
    with pytest.raises(CommonServerPython.DemistoException):
        FortiSandboxv2.submission_file_upload_command({"comment": "0" * 256}, mock_client)


@pytest.mark.parametrize(
    "args",
    [
        ({"comment": "0" * 256}),
        ({"depth": "6"}),
        ({"process_timeout": "29"}),
    ],
)
def test_submission_url_upload_command_error(mock_client: FortiSandboxv2.Client, args: dict[str, Any]):
    """Test the submission_url_upload_command function with an error.

    Ensure:
    - A DemistoException is raised.

    Args:
        mock_client (FortiSandboxv2.Client): Mocked client.
        args (dict[str, Any]): The arguments to pass to the function.
    """
    with pytest.raises(CommonServerPython.DemistoException):
        FortiSandboxv2.submission_url_upload_command(args, mock_client)


def test_submission_cancel_command(requests_mock, mock_client: FortiSandboxv2.Client):
    """Test the submission_cancel_command function.

    Ensure:
    - The readable output is set to the expected readable output.
    - The raw response is set to the expected raw response.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
    """
    sid = "123"
    mock_raw_response = {"result": {"status": {"code": 0}}}
    requests_mock.post(API_URL, json=mock_raw_response)

    command_results = FortiSandboxv2.submission_cancel_command(mock_client, {"id": sid})

    assert command_results.readable_output == f"## The cancellation of the submission {sid} was successfully sent."
    assert command_results.raw_response == mock_raw_response


def test_submission_job_verdict_command(requests_mock, mock_client: FortiSandboxv2.Client):
    """Test the submission_job_verdict_command function.

    Ensure:
    - The command results is set to the expected command results value.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
    """
    url = "www.pokemon.com"
    jid = "123"
    mock_raw_response = load_mock_response("job_verdict.json")
    indicator = CommonServerPython.Common.URL(
        dbot_score=CommonServerPython.Common.DBotScore(
            indicator=url,
            indicator_type=CommonServerPython.DBotScoreType.URL,
            score=CommonServerPython.Common.DBotScore.GOOD,
            reliability=FortiSandboxv2.RELIABILITY,
            message="https://pikachu/job-detail/?sid=456&jid=123&req_type=url-csearch",
        ),
        url=url,
    )
    requests_mock.post(API_URL, json=mock_raw_response)

    command_results = FortiSandboxv2.submission_job_verdict_command(mock_client, {"id": jid})

    mock_raw_response["result"]["data"]["name"] = url

    assert command_results.outputs_prefix == "FortiSandbox.Submission"
    assert command_results.outputs_key_field == "jid"
    assert command_results.outputs == {**mock_raw_response["result"]["data"], "name": url}
    assert command_results.readable_output.startswith(f"### The verdict for the job {jid}:")
    assert command_results.raw_response == mock_raw_response
    assert command_results.indicator.to_context() == indicator.to_context()


def test_submission_job_list_command(requests_mock, mock_client: FortiSandboxv2.Client):
    """Test the submission_job_list_command function.

    Ensure:
    - The command results is set to the expected command results value.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
    """
    sid = "123"
    mock_raw_response = load_mock_response("job_list_done.json")
    requests_mock.post(API_URL, json=mock_raw_response)

    command_results = FortiSandboxv2.submission_job_list_command(mock_client, {"id": sid})

    assert command_results.outputs_prefix == "FortiSandbox.Submission"
    assert command_results.outputs_key_field == "jid"
    assert command_results.outputs == [{"sid": sid, "jid": jid} for jid in ["000", "111", "222"]]
    assert command_results.readable_output.startswith(f"### The submission {sid} job IDs:")
    assert command_results.raw_response == mock_raw_response


@pytest.mark.parametrize(
    "identifier",
    ["123", "0000000000000000000000000000000000000000000000000000000000000000"],
)
def test_submission_job_report_command(requests_mock, mock_client: FortiSandboxv2.Client, identifier: str):
    """Test the submission_job_report_command function.

    Ensure:
    - The file result is set to the expected file result value.
    - The file content is set to the expected file content value.

    Args:
        requests_mock (pytest_mock.plugin.MockerFixture): Mocked requests.
        mock_client (FortiSandboxv2.Client): Mocked client
        identifier (str): The identifier for the job.
    """
    mock_raw_response = load_mock_response("job_report.json")
    requests_mock.post(API_URL, json=mock_raw_response)

    # Use mock_open to simulate file operations
    mock_open = unittest.mock.mock_open()
    with (
        unittest.mock.patch("builtins.open", mock_open),
        unittest.mock.patch("FortiSandboxv2.demisto.uniqueFile", return_value="mock_file_id"),
    ):
        file_result = FortiSandboxv2.submission_job_report_command(mock_client, {"identifier": identifier})
        handle = mock_open()

        assert file_result == {
            "Contents": "",
            "ContentsFormat": "text",
            "Type": CommonServerPython.EntryType.ENTRY_INFO_FILE,
            "File": "pikachu.pdf",
            "FileID": file_result["FileID"],
        }
        # Retrieve the write calls to the mock file and assert the content
        assert handle.write.call_args[0][0] == b"Decoded Pikachu"


def test_is_url_by_ftype():
    """Test URL detection using ftype field"""
    data = {"ftype": "WEBLink", "name": "not a url", "download_url": ""}
    assert is_url(data) is True


def test_is_url_by_http_name():
    """Test URL detection using http name field"""
    data = {"ftype": "File", "name": "http://example.com", "download_url": ""}
    assert is_url(data) is True


def test_is_url_by_https_name():
    """Test URL detection using https name field"""
    data = {"ftype": "File", "name": "https://example.com", "download_url": ""}
    assert is_url(data) is True


def test_is_url_empty_data():
    """Test with empty data"""
    assert is_url({}) is False


def test_is_url_missing_fields():
    """Test with missing fields"""
    data = {
        "name": "http://example.com"  # Missing other fields
    }
    assert is_url(data) is True  # Should still detect from name

    data = {
        "ftype": "File"  # Only has ftype
    }
    assert is_url(data) is False


def test_build_indicator_unknown_type():
    """
    Test the build_indicator function with unknown indicator type.

    Ensure:
    - A ValueError is raised when the indicator type cannot be determined.
    """
    from FortiSandboxv2 import build_indicator

    # Test data that doesn't contain URL or file hash information
    test_data = {
        "score": 3,
        "malware_name": "test_malware",
        "detail_url": "https://example.com/detail?sid=123",
        "category": "test_category",
    }

    # The error message we expect to see
    expected_error = "Could not determine indicator type"

    # Verify that the function raises a ValueError with the expected message
    with pytest.raises(CommonServerPython.DemistoException) as exc_info:
        build_indicator(data=test_data, url=None, file_hash=None)

    # Check that the error message contains our expected text
    assert expected_error in str(exc_info.value)