CriblSearch

Cribl Search allows you to query, retrieve, and manage search jobs, datasets, and saved searches across your Cribl Cloud deployment.

IT Services · Cribl

Details

IDCriblSearch
ProviderCribl
CategoryIT Services
From Version6.10.0
Docker Imagedemisto/fastapi:0.125.0.10158186

README

Cribl Search is a search solution that allows you to query, retrieve, and manage search jobs, datasets, and saved searches across your Cribl Cloud deployment.
This integration was integrated and tested with version 4.17.0 of Cribl API.

Configure CriblSearch in Cortex

Parameter Description Required
Base URL The base URL assigned to your organization: https://${workspaceName}-${organizationId}.cribl.cloud True
Client ID   True
Client Secret   True
Trust any certificate (not secure)   False
Use system proxy settings   False

Commands

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

cribl-search-query


Runs a search query against Cribl Search and returns results.

Base Command

cribl-search-query

Input

Argument Name Description Required
query_id The ID of a saved query to execute. Optional
job_id The ID of an existing search job to retrieve results from. Optional
query The search query string to execute. Optional
earliest The start time for the search, in relative time or epoch seconds. Optional
latest The end time for the search, in relative time or epoch seconds. Optional
sample_rate The probability (0-1) of including each matching event (for example, 0.1 returns ~10%). If omitted, no sampling is applied. Optional
force Whether to force execution of a scheduled query. Optional
page The page number for pagination. Optional
limit The maximum number of results to return. Default is 50. Optional

Context Output

Path Type Description
Cribl.SearchQuery.events Unknown The list of events returned by the search (parsed from the NDJSON response). May be empty when the job is still queued/running.
Cribl.SearchQuery.isFinished Boolean Whether the search query has finished executing.
Cribl.SearchQuery.job Object The search job metadata associated with this query.
Cribl.SearchQuery.job.id String The unique identifier of the search job that produced these results.
Cribl.SearchQuery.job.query String The search query string executed by the job.
Cribl.SearchQuery.job.status String The current status of the search job (for example, queued, running, completed).
Cribl.SearchQuery.job.timeCreated Number The epoch (ms) when the search job was created.
Cribl.SearchQuery.offset Number The offset within the result set used for pagination.
Cribl.SearchQuery.persistedEventCount Number The number of events persisted in the result set.
Cribl.SearchQuery.totalEventCount Number Total number of events matched by the query.

Command example

!cribl-search-query query="dataset=\"cribl_search_sample\" | project method, source, status, url | take 5" earliest="-24h" latest="now" limit=3

Context Example

{
    "Cribl": {
        "SearchQuery": {
            "events": [],
            "isFinished": false,
            "job": {
                "earliest": "-24h",
                "id": "1777447153600.MgWe3v",
                "latest": "now",
                "query": "dataset=\"cribl_search_sample\" | project method, source, status, url | take 5",
                "status": "queued",
                "timeCreated": 1777447153600
            },
            "limit": 3,
            "offset": 0,
            "persistedEventCount": 0,
            "totalEventCount": 0
        }
    }
}

Human Readable Output

Search Query - Job Info

Is Finished Job ID Status Query Earliest Latest Total Events
false 1777447153600.MgWe3v queued dataset=”cribl_search_sample” | project method, source, status, url | take 5 -24h now 0

cribl-search-status


Retrieves the status of a specific search job.

Base Command

cribl-search-status

Input

Argument Name Description Required
job_id The unique identifier of the search job. Required

Context Output

Path Type Description
Cribl.SearchStatus.pendingComputeNodeStatuses Object The counts of pending compute nodes (warm/cold) for the job.
Cribl.SearchStatus.status String The current status of the search job (for example, queued, running, completed).
Cribl.SearchStatus.timeCreated Number The epoch (ms) when the search job was created.
Cribl.SearchStatus.timeStarted Number The epoch (ms) when the search job started executing. This is only set once the job leaves the queued state.
Cribl.SearchStatus.timeCompleted Number The epoch (ms) when the search job completed. This is only set after the job finishes.

Command example

!cribl-search-status job_id="1777207943198.pb0ZZ0"

Context Example

{
    "Cribl": {
        "SearchStatus": {
            "cacheStatusesByStageId": {
                "root": {
                    "cribl_search_sample": {
                        "cacheStatus": "miss",
                        "computeType": "v1",
                        "reason": "Not a Lake Dataset",
                        "usedCache": false
                    }
                }
            },
            "pendingComputeNodeStatuses": {
                "countCold": 0,
                "countWarm": 0
            },
            "status": "completed",
            "timeCompleted": 1777207949675,
            "timeCreated": 1777207943198,
            "timeNow": 1777447157205,
            "timeStarted": 1777207943675
        }
    }
}

Human Readable Output

Search Job 1777207943198.pb0ZZ0 Status

Status Time Started Time Created Time Completed
completed 1777207943675 1777207943198 1777207949675

cribl-search-result


Retrieves the results of a completed search job.

Base Command

cribl-search-result

Input

Argument Name Description Required
job_id The unique identifier of the search job. Required
lower_bound The lower time bound for results (inclusive, epoch). Optional
upper_bound The upper time bound for results (exclusive, epoch). Optional
page The page number for pagination. Optional
limit The maximum number of results to return. Default is 50. Optional
all_results Whether to return all results. If true, overrides the limit argument. Optional

Context Output

Path Type Description
Cribl.SearchResult.events Unknown The list of events returned by the search (parsed from the NDJSON response). Each element is a free-form event object whose shape depends on the dataset and the query’s projection.
Cribl.SearchResult.isFinished Boolean Whether the search job has finished executing.
Cribl.SearchResult.job Object The search job metadata associated with these results.
Cribl.SearchResult.job.id String The unique identifier of the search job.
Cribl.SearchResult.job.query String The search query string executed by the job.
Cribl.SearchResult.job.status String The current status of the search job (for example, queued, running, completed).
Cribl.SearchResult.job.timeCreated Number The epoch (ms) when the search job was created.
Cribl.SearchResult.offset Number The offset within the result set used for pagination.
Cribl.SearchResult.persistedEventCount Number The number of events persisted in the result set.
Cribl.SearchResult.totalEventCount Number The total number of events matched by the search job.

Command example

!cribl-search-result job_id="1777207943198.pb0ZZ0" limit=5

Context Example

{
    "Cribl": {
        "SearchResult": {
            "events": [
                {
                    "source": "s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0RRoVn.2.raw.gz"
                },
                {
                    "source": "s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0ZBHzD.2.raw.gz"
                },
                {
                    "source": "s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-2w9JEP.2.raw.gz"
                },
                {
                    "source": "s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0RRoVn.2.raw.gz"
                },
                {
                    "source": "s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0ZBHzD.2.raw.gz"
                }
            ],
            "isFinished": true,
            "job": {
                "earliest": "-24h",
                "id": "1777207943198.pb0ZZ0",
                "latest": "now",
                "query": "dataset=\"cribl_search_sample\" | project method, source, status, url | take 5",
                "status": "completed",
                "timeCompleted": 1777207949675,
                "timeCreated": 1777207943198,
                "timeStarted": 1777207943675
            },
            "limit": 5,
            "offset": 0,
            "persistedEventCount": 5,
            "totalEventCount": 5
        }
    }
}

Human Readable Output

Search Job 1777207943198.pb0ZZ0 Results - Job Info

Is Finished Job ID Status Query Earliest Latest Total Events
true 1777207943198.pb0ZZ0 completed dataset=”cribl_search_sample” | project method, source, status, url | take 5 -24h now 5

Search Job 1777207943198.pb0ZZ0 Results - Events

source
s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0RRoVn.2.raw.gz
s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0ZBHzD.2.raw.gz
s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-2w9JEP.2.raw.gz
s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0RRoVn.2.raw.gz
s3://cribl-search-example/data/vpcflowlogs/2026/04/26/12/CriblOut-0ZBHzD.2.raw.gz

cribl-search-job-create


Creates a new search job in Cribl Search.

Base Command

cribl-search-job-create

Input

Argument Name Description Required
query The search query string. Required
earliest The start time for the search, in epoch seconds. Optional
latest The end time for the search, in epoch seconds. Optional
sample_rate The probability (0-1) of including each matching event (for example, 0.1 returns ~10%). If omitted, no sampling is applied. Optional
num_events_before The number of events to include before the target event. Optional
num_events_after The number of events to include after the target event. Optional
target_event_time The target event time (epoch seconds). Optional
is_private Whether the search job is private. Default is True. Optional
set_options A JSON string of additional search options. Optional
expected_output_type The expected output type for the search. Optional

Context Output

Path Type Description
Cribl.SearchJob.id String The unique identifier of the search job.
Cribl.SearchJob.user String The user identifier (client ID) that created the job.
Cribl.SearchJob.displayUsername String The display name of the user who created the job.
Cribl.SearchJob.group String The search group the job belongs to.
Cribl.SearchJob.query String The search query string executed by the job.
Cribl.SearchJob.status String The current status of the search job (for example, queued, running, completed).
Cribl.SearchJob.timeCreated Number The epoch (ms) when the search job was created.
Cribl.SearchJob.type String The type of search job (for example, standard, dashboard).
Cribl.SearchJob.usageGroupId String The identifier of the usage group the job is billed against.
Cribl.SearchJob.isPrivate Boolean Whether the search job is marked private.
Cribl.SearchJob.accelerated Boolean Whether the search job uses acceleration.
Cribl.SearchJob.earliest String The start time for the search, in relative time or epoch seconds.
Cribl.SearchJob.latest String The end time for the search, in relative time or epoch seconds.
Cribl.SearchJob.compatibilityChecks Object The compatibility check flags evaluated for the job.
Cribl.SearchJob.metadata Object The metadata about the query (for example, datasets, providers, operators, and functions).
Cribl.SearchJob.setOptions Object The additional search options provided when creating the job.
Cribl.SearchJob.stages Unknown The stages of the search job’s execution plan.
Cribl.SearchJob.internal Object The internal job state (compiled policies, role-derived limits, preprocessed query, etc.). Returned on create; not normally returned by list/update.
Cribl.SearchJob.userDetails Object The details about the user/credential that created the job.
Cribl.SearchJob.userDetails.email String The email address of the user who created the job.
Cribl.SearchJob.userDetails.username String The username of the user (or client ID, for API-credential users) who created the job.
Cribl.SearchJob.userDetails.displayUsername String The display name of the user who created the job.
Cribl.SearchJob.userDetails.type String The type of user identity (for example, apiCredential, sso).
Cribl.SearchJob.userDetails.roles Unknown The roles assigned to the user.

Command example

!cribl-search-job-create query="dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr"

Context Example

{
    "Cribl": {
        "SearchJob": {
            "accelerated": false,
            "compatibilityChecks": {
                "datatypes": false
            },
            "displayUsername": "example.user@example.com",
            "earliest": "-1h",
            "group": "default_search",
            "id": "1777447149939.xPWbOm",
            "internal": {
                "compiledPolicies": [
                    {
                        "actions": [
                            "*"
                        ],
                        "object": "*"
                    },
                    {
                        "actions": [
                            "GET"
                        ],
                        "object": "/system/users/EXAMPLECLIENTID0000000000000000@clients"
                    },
                    {
                        "actions": [
                            "PATCH"
                        ],
                        "object": "/system/users/EXAMPLECLIENTID0000000000000000@clients/info"
                    }
                ],
                "detectedKeyAccesses": {},
                "email": "example.user@example.com",
                "maxExecutors": 50,
                "maxResultsPerSearch": 50000,
                "maxRunningTimeRange": {
                    "maxSec": 86400
                },
                "preprocessedQuery": "dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr",
                "roles": [
                    "search_user",
                    "org_user",
                    "ws_user"
                ]
            },
            "isPrivate": true,
            "latest": "now",
            "metadata": {
                "arguments": {},
                "cloudProvider": "aws",
                "computeTypes": {
                    "v1": 1
                },
                "datasets": {
                    "cribl_search_sample": 1
                },
                "functions": {
                    "count": 1
                },
                "operators": {
                    "dataset=\"cribl_search_sample\"": 1,
                    "summarize": 1
                },
                "providerTypes": {
                    "s3": 1
                },
                "providers": {
                    "cribl_s3sample_provider": 1
                }
            },
            "query": "dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr",
            "setOptions": {},
            "stages": [
                {
                    "dependencies": [],
                    "filter": "(dataset == 'cribl_search_sample')",
                    "id": "root",
                    "resolvedDatasetIds": [
                        "cribl_search_sample"
                    ],
                    "searchConfig": {
                        "canComputeMetadataDistributively": false,
                        "datasets": [
                            "cribl_search_sample"
                        ],
                        "hasSendOperator": false,
                        "logicalPlans": {
                            "Combined": {
                                "root:0:2uw2": [
                                    {
                                        "condition": {
                                            "caseSensitive": false,
                                            "lhs": {
                                                "columnPath": [
                                                    "dataset"
                                                ],
                                                "type": "identifier"
                                            },
                                            "operator": "==",
                                            "rhs": {
                                                "literal": "cribl_search_sample",
                                                "type": "literal"
                                            },
                                            "type": "binaryOperation"
                                        },
                                        "type": "filter"
                                    }
                                ],
                                "root:1:tTTC": [
                                    {
                                        "aggregates": [
                                            {
                                                "assignee": {
                                                    "columnPath": [
                                                        "cnt"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                "operation": {
                                                    "functionType": "aggregation",
                                                    "name": "count",
                                                    "parameters": [],
                                                    "type": "function"
                                                },
                                                "type": "assign"
                                            }
                                        ],
                                        "aggregationType": "summarize",
                                        "canDistributeAggregation": false,
                                        "groupBy": [
                                            {
                                                "columnPath": [
                                                    "srcaddr"
                                                ],
                                                "type": "identifier"
                                            }
                                        ],
                                        "isPreviewableOperation": true,
                                        "location": "coordinated",
                                        "type": "aggregate"
                                    }
                                ],
                                "root:3:uDgk": [
                                    {
                                        "type": "noop"
                                    }
                                ]
                            },
                            "Coordinated": {
                                ...
                            },
                            "Federated": {
                                ...
                            }
                        },
                        "orderedFieldNames": [
                            "srcaddr",
                            "cnt"
                        ],
                        "pipelines": {
                            "Combined": {
                                "conf": {
                                    "asyncFuncTimeout": 1000,
                                    "description": "Pipeline, generated from Kalipso query",
                                    "functions": [
                                        {
                                            "canFullyPushToFederated": true,
                                            "conf": {},
                                            "description": "dataset=\"cribl_search_sample\"",
                                            "disabled": false,
                                            "filter": "!(dataset == 'cribl_search_sample')",
                                            "final": false,
                                            "functionInstanceId": "root:0:2uw2",
                                            "id": "drop"
                                        },
                                        {
                                            "canFullyPushToFederated": false,
                                            "conf": {
                                                "aggregations": [
                                                    "count().as(cnt)"
                                                ],
                                                "cumulative": true,
                                                "flushOnInputClose": false,
                                                "groupbys": [
                                                    "srcaddr"
                                                ],
                                                "metricsMode": false,
                                                "preserveGroupBys": true,
                                                "printUndefineds": true,
                                                "searchAggMode": "Coordinated",
                                                "sufficientStatsOnly": false,
                                                "timeWindow": "1s"
                                            },
                                            "description": "summarize cnt=count() by srcaddr",
                                            "disabled": false,
                                            "filter": "true",
                                            "final": false,
                                            "functionInstanceId": "root:1:tTTC",
                                            "id": "aggregation"
                                        },
                                        {
                                            "canFullyPushToFederated": true,
                                            "conf": {
                                                "keep": [
                                                    "cnt",
                                                    "cnt.*",
                                                    "srcaddr",
                                                    "srcaddr.*"
                                                ],
                                                "printUndefineds": true,
                                                "remove": [
                                                    "*"
                                                ]
                                            },
                                            "description": "summarize cnt=count() by srcaddr",
                                            "disabled": false,
                                            "filter": "true",
                                            "final": false,
                                            "functionInstanceId": "root:3:uDgk",
                                            "id": "eval"
                                        }
                                    ]
                                },
                                "id": "root"
                            },
                            "Coordinated": {
                                ...
                            },
                            "Federated": {
                                ...
                            }
                        },
                        "referencedColumnPaths": [
                            [
                                "cnt"
                            ],
                            [
                                "srcaddr"
                            ]
                        ],
                        "searchTerms": [],
                        "useFormattedVisualization": true
                    },
                    "searchVersionByDatasetId": {},
                    "status": "new",
                    "subQueryText": "dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr"
                }
            ],
            "status": "queued",
            "timeCreated": 1777447149939,
            "type": "standard",
            "usageGroupId": "default",
            "user": "EXAMPLECLIENTID0000000000000000@clients",
            "userDetails": {
                "apiCredential": {
                    "clientId": "EXAMPLECLIENTID0000000000000000@clients",
                    "createdBy": "example.user@example.com",
                    "name": "example.user@example.com"
                },
                "displayUsername": "example.user@example.com",
                "email": "example.user@example.com",
                "roles": [
                    "search_user",
                    "org_user",
                    "ws_user"
                ],
                "ssoGroups": [],
                "type": "apiCredential",
                "username": "EXAMPLECLIENTID0000000000000000@clients"
            }
        }
    }
}

Human Readable Output

Search Job Created

User ID Is Private Type Status
EXAMPLECLIENTID0000000000000000@clients 1777447149939.xPWbOm true standard queued

cribl-search-job-list


Retrieves a list of search jobs or details of a specific search job.

Base Command

cribl-search-job-list

Input

Argument Name Description Required
job_id The unique identifier of a specific search job to retrieve. Optional
limit The maximum number of results to return. Default is 10. Optional
all_results Whether to return all results. If true, overrides the limit argument. Optional

Context Output

Path Type Description
Cribl.SearchJob.id String Unique identifier of the search job.
Cribl.SearchJob.user String User identifier (client ID) that created the job.
Cribl.SearchJob.displayUsername String Display name of the user who created the job.
Cribl.SearchJob.group String Search group the job belongs to.
Cribl.SearchJob.query String The search query string executed by the job.
Cribl.SearchJob.status String Current status of the search job (e.g., queued, running, completed).
Cribl.SearchJob.timeCreated Number Epoch (ms) when the search job was created.
Cribl.SearchJob.timeStarted Number Epoch (ms) when the search job started executing.
Cribl.SearchJob.timeCompleted Number Epoch (ms) when the search job completed.
Cribl.SearchJob.type String Type of search job (e.g., standard, dashboard).
Cribl.SearchJob.isPrivate Boolean Whether the search job is marked private.
Cribl.SearchJob.accelerated Boolean Whether the search job uses acceleration.
Cribl.SearchJob.earliest String Earliest time boundary for the search (relative time string or epoch seconds).
Cribl.SearchJob.earliestEpoch Number Resolved earliest time boundary in epoch milliseconds.
Cribl.SearchJob.latest String Latest time boundary for the search (relative time string or epoch seconds).
Cribl.SearchJob.latestEpoch Number Resolved latest time boundary in epoch milliseconds.
Cribl.SearchJob.cpuMetrics Object CPU usage metrics for the executed job (billable seconds, per-executor breakdown, totals).
Cribl.SearchJob.compatibilityChecks Object Compatibility check flags evaluated for the job.
Cribl.SearchJob.metadata Object Metadata about the query (datasets, providers, operators, functions, etc.).
Cribl.SearchJob.setOptions Object Additional search options provided when creating the job.
Cribl.SearchJob.stages Unknown Stages of the search job’s execution plan, including per-stage cache status and search config.

Command example

!cribl-search-job-list limit=3

Context Example

{
    "Cribl": {
        "SearchJob": [
            {
                "accelerated": false,
                "compatibilityChecks": {
                    "datatypes": false
                },
                "cpuMetrics": {
                    "billableCPUSeconds": 24.78200000000004,
                    "executorsCPUSeconds": {
                        "23ywr3HV": 0.621,
                        "2C3c5u1h": 0.619,
                        "2G7pjwPk": 0.6,
                        "COORDINATOR": 5.817,
                        ...
                    },
                    "totalCPUSeconds": 24.78200000000004,
                    "totalExecCPUSeconds": 24.78200000000004
                },
                "displayUsername": "example.user@example.com",
                "earliest": "-24h",
                "earliestEpoch": 1777121543198,
                "group": "default_search",
                "id": "1777207943198.pb0ZZ0",
                "isPrivate": true,
                "latest": "now",
                "latestEpoch": 1777207943198,
                "metadata": {
                    "arguments": {},
                    "cloudProvider": "aws",
                    "computeTypes": {
                        "v1": 1
                    },
                    "datasets": {
                        "cribl_search_sample": 1
                    },
                    "functions": {},
                    "operators": {
                        "dataset=\"cribl_search_sample\"": 1,
                        "project": 1,
                        "take": 1
                    },
                    "providerTypes": {
                        "s3": 1
                    },
                    "providers": {
                        "cribl_s3sample_provider": 1
                    }
                },
                "query": "dataset=\"cribl_search_sample\" | project method, source, status, url | take 5",
                "setOptions": {},
                "stages": [
                    {
                        "cacheStatusByDatasetId": {
                            "cribl_search_sample": {
                                "cacheStatus": "miss",
                                "computeType": "v1",
                                "reason": "Not a Lake Dataset",
                                "usedCache": false
                            }
                        },
                        "dependencies": [],
                        "filter": "(dataset == 'cribl_search_sample')",
                        "id": "root",
                        "resolvedDatasetIds": [
                            "cribl_search_sample"
                        ],
                        "searchConfig": {
                            "canComputeMetadataDistributively": false,
                            "datasets": [
                                "cribl_search_sample"
                            ],
                            "hasSendOperator": false,
                            "logicalPlans": {
                                "Combined": {
                                    "root:0:HEER": [
                                        {
                                            "condition": {
                                                "caseSensitive": false,
                                                "lhs": {
                                                    "columnPath": [
                                                        "dataset"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                "operator": "==",
                                                "rhs": {
                                                    "literal": "cribl_search_sample",
                                                    "type": "literal"
                                                },
                                                "type": "binaryOperation"
                                            },
                                            "type": "filter"
                                        }
                                    ],
                                    "root:1:UBPo": [
                                        {
                                            "add": [
                                                {
                                                    "columnPath": [
                                                        "method"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                {
                                                    "columnPath": [
                                                        "source"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                {
                                                    "columnPath": [
                                                        "status"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                {
                                                    "columnPath": [
                                                        "url"
                                                    ],
                                                    "type": "identifier"
                                                }
                                            ],
                                            "removeOthers": true,
                                            "type": "project"
                                        }
                                    ],
                                    "root:3:Xm06": [
                                        {
                                            "limit": 5,
                                            "type": "limit"
                                        }
                                    ]
                                },
                                "Coordinated": {
                                    ...
                                },
                                "Federated": {
                                    ...
                                }
                            },
                            "orderedFieldNames": [
                                "method",
                                "source",
                                "status",
                                "url"
                            ],
                            "pipelines": {
                                "Combined": {
                                    "conf": {
                                        "asyncFuncTimeout": 1000,
                                        "description": "Pipeline, generated from Kalipso query",
                                        "functions": [
                                            {
                                                "canFullyPushToFederated": true,
                                                "conf": {},
                                                "description": "dataset=\"cribl_search_sample\"",
                                                "disabled": false,
                                                "filter": "!(dataset == 'cribl_search_sample')",
                                                "final": false,
                                                "functionInstanceId": "root:0:HEER",
                                                "id": "drop"
                                            },
                                            {
                                                "canFullyPushToFederated": false,
                                                "conf": {
                                                    "limit": 5
                                                },
                                                "description": "take 5",
                                                "disabled": false,
                                                "filter": "true",
                                                "final": false,
                                                "functionInstanceId": "root:3:Xm06",
                                                "id": "limit"
                                            },
                                            {
                                                "canFullyPushToFederated": true,
                                                "conf": {
                                                    "keep": [
                                                        "method",
                                                        "method.*",
                                                        "source",
                                                        "source.*",
                                                        "status",
                                                        "status.*",
                                                        "url",
                                                        "url.*"
                                                    ],
                                                    "printUndefineds": true,
                                                    "remove": [
                                                        "*"
                                                    ]
                                                },
                                                "description": "project method, source, status, url",
                                                "disabled": false,
                                                "filter": "true",
                                                "final": false,
                                                "functionInstanceId": "root:1:UBPo",
                                                "id": "eval"
                                            }
                                        ]
                                    },
                                    "id": "root"
                                },
                                "Coordinated": {
                                    ...
                                },
                                "Federated": {
                                    ...
                                }
                            },
                            "referencedColumnPaths": [
                                [
                                    "method"
                                ],
                                [
                                    "source"
                                ],
                                [
                                    "status"
                                ],
                                [
                                    "url"
                                ]
                            ],
                            "searchTerms": [],
                            "useFormattedVisualization": true
                        },
                        "searchVersionByDatasetId": {},
                        "status": "completed",
                        "subQueryText": "dataset=\"cribl_search_sample\" | project method, source, status, url | take 5"
                    }
                ],
                "status": "completed",
                "timeCompleted": 1777207949675,
                "timeCreated": 1777207943198,
                "timeStarted": 1777207943675,
                "type": "dashboard",
                "user": "EXAMPLECLIENTID0000000000000000@clients"
            },
            ...
        ]
    }
}

Human Readable Output

Search Jobs List

User ID Is Private Type Status
EXAMPLECLIENTID0000000000000000@clients 1777207943198.pb0ZZ0 true dashboard completed
EXAMPLECLIENTID0000000000000000@clients 1777208015306.F0hxMo true dashboard completed
EXAMPLECLIENTID0000000000000000@clients 1777208286161.tTkDeJ true dashboard completed

cribl-search-job-update


Updates a search job’s status or privacy setting. At least one of status or is_private must be provided.

Base Command

cribl-search-job-update

Input

Argument Name Description Required
job_id The unique identifier of the search job to update. Required
status The new status for the search job (e.g., completed, canceled). Optional
is_private Whether the search job should be private. Optional

Context Output

Path Type Description
Cribl.SearchJob.id String Unique identifier of the search job.
Cribl.SearchJob.user String User identifier (client ID) that created the job.
Cribl.SearchJob.displayUsername String Display name of the user who created the job.
Cribl.SearchJob.group String Search group the job belongs to.
Cribl.SearchJob.query String The search query string executed by the job.
Cribl.SearchJob.status String Current status of the search job (e.g., queued, running, completed).
Cribl.SearchJob.timeCreated Number Epoch (ms) when the search job was created.
Cribl.SearchJob.timeStarted Number Epoch (ms) when the search job started executing.
Cribl.SearchJob.timeCompleted Number Epoch (ms) when the search job completed.
Cribl.SearchJob.type String Type of search job (e.g., standard, dashboard).
Cribl.SearchJob.isPrivate Boolean Whether the search job is marked private.
Cribl.SearchJob.accelerated Boolean Whether the search job uses acceleration.
Cribl.SearchJob.earliest String Earliest time boundary for the search (relative time string or epoch seconds).
Cribl.SearchJob.earliestEpoch Number Resolved earliest time boundary in epoch milliseconds.
Cribl.SearchJob.latest String Latest time boundary for the search (relative time string or epoch seconds).
Cribl.SearchJob.latestEpoch Number Resolved latest time boundary in epoch milliseconds.
Cribl.SearchJob.compatibilityChecks Object Compatibility check flags evaluated for the job.
Cribl.SearchJob.metadata Object Metadata about the query (datasets, providers, operators, functions, etc.).
Cribl.SearchJob.setOptions Object Additional search options provided when creating the job.
Cribl.SearchJob.stages Unknown Stages of the search job’s execution plan, including per-stage cache status and search config.
Cribl.SearchJob.userDetails Object Details about the user/credential that created the job.
Cribl.SearchJob.userDetails.email String Email address of the user who created the job.
Cribl.SearchJob.userDetails.username String Username of the user (or client ID, for API-credential users) who created the job.
Cribl.SearchJob.userDetails.displayUsername String Display name of the user who created the job.
Cribl.SearchJob.userDetails.type String Type of user identity (e.g., apiCredential, sso).
Cribl.SearchJob.userDetails.roles Unknown Roles assigned to the user.

Command example

!cribl-search-job-update job_id="1777446985069.KSZQ5h" is_private=true

Context Example

{
    "Cribl": {
        "SearchJob": {
            "accelerated": false,
            "compatibilityChecks": {
                "datatypes": false
            },
            "displayUsername": "example.user@example.com",
            "earliest": "-1h",
            "earliestEpoch": 1777443385069,
            "group": "default_search",
            "id": "1777446985069.KSZQ5h",
            "isPrivate": true,
            "latest": "now",
            "latestEpoch": 1777446985069,
            "metadata": {
                "arguments": {},
                "cloudProvider": "aws",
                "computeTypes": {
                    "v1": 1
                },
                "datasets": {
                    "cribl_search_sample": 1
                },
                "functions": {
                    "count": 1
                },
                "operators": {
                    "dataset=\"cribl_search_sample\"": 1,
                    "summarize": 1
                },
                "providerTypes": {
                    "s3": 1
                },
                "providers": {
                    "cribl_s3sample_provider": 1
                }
            },
            "query": "dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr",
            "setOptions": {},
            "stages": [
                {
                    "cacheStatusByDatasetId": {
                        "cribl_search_sample": {
                            "cacheStatus": "miss",
                            "computeType": "v1",
                            "reason": "Not a Lake Dataset",
                            "usedCache": false
                        }
                    },
                    "dependencies": [],
                    "filter": "(dataset == 'cribl_search_sample')",
                    "id": "root",
                    "resolvedDatasetIds": [
                        "cribl_search_sample"
                    ],
                    "searchConfig": {
                        "canComputeMetadataDistributively": false,
                        "datasets": [
                            "cribl_search_sample"
                        ],
                        "hasSendOperator": false,
                        "logicalPlans": {
                            "Combined": {
                                "root:0:R25N": [
                                    {
                                        "condition": {
                                            "caseSensitive": false,
                                            "lhs": {
                                                "columnPath": [
                                                    "dataset"
                                                ],
                                                "type": "identifier"
                                            },
                                            "operator": "==",
                                            "rhs": {
                                                "literal": "cribl_search_sample",
                                                "type": "literal"
                                            },
                                            "type": "binaryOperation"
                                        },
                                        "type": "filter"
                                    }
                                ],
                                "root:1:TFOw": [
                                    {
                                        "aggregates": [
                                            {
                                                "assignee": {
                                                    "columnPath": [
                                                        "cnt"
                                                    ],
                                                    "type": "identifier"
                                                },
                                                "operation": {
                                                    "functionType": "aggregation",
                                                    "name": "count",
                                                    "parameters": [],
                                                    "type": "function"
                                                },
                                                "type": "assign"
                                            }
                                        ],
                                        "aggregationType": "summarize",
                                        "canDistributeAggregation": false,
                                        "groupBy": [
                                            {
                                                "columnPath": [
                                                    "srcaddr"
                                                ],
                                                "type": "identifier"
                                            }
                                        ],
                                        "isPreviewableOperation": true,
                                        "location": "coordinated",
                                        "type": "aggregate"
                                    }
                                ],
                                "root:3:zikB": [
                                    {
                                        "type": "noop"
                                    }
                                ]
                            },
                            "Coordinated": {
                                ...
                            },
                            "Federated": {
                                ...
                            }
                        },
                        "orderedFieldNames": [
                            "srcaddr",
                            "cnt"
                        ],
                        "pipelines": {
                            "Combined": {
                                "conf": {
                                    "asyncFuncTimeout": 1000,
                                    "description": "Pipeline, generated from Kalipso query",
                                    "functions": [
                                        {
                                            "canFullyPushToFederated": true,
                                            "conf": {},
                                            "description": "dataset=\"cribl_search_sample\"",
                                            "disabled": false,
                                            "filter": "!(dataset == 'cribl_search_sample')",
                                            "final": false,
                                            "functionInstanceId": "root:0:R25N",
                                            "id": "drop"
                                        },
                                        {
                                            "canFullyPushToFederated": false,
                                            "conf": {
                                                "aggregations": [
                                                    "count().as(cnt)"
                                                ],
                                                "cumulative": true,
                                                "flushOnInputClose": false,
                                                "groupbys": [
                                                    "srcaddr"
                                                ],
                                                "metricsMode": false,
                                                "preserveGroupBys": true,
                                                "printUndefineds": true,
                                                "searchAggMode": "Coordinated",
                                                "sufficientStatsOnly": false,
                                                "timeWindow": "1s"
                                            },
                                            "description": "summarize cnt=count() by srcaddr",
                                            "disabled": false,
                                            "filter": "true",
                                            "final": false,
                                            "functionInstanceId": "root:1:TFOw",
                                            "id": "aggregation"
                                        },
                                        {
                                            "canFullyPushToFederated": true,
                                            "conf": {
                                                "keep": [
                                                    "cnt",
                                                    "cnt.*",
                                                    "srcaddr",
                                                    "srcaddr.*"
                                                ],
                                                "printUndefineds": true,
                                                "remove": [
                                                    "*"
                                                ]
                                            },
                                            "description": "summarize cnt=count() by srcaddr",
                                            "disabled": false,
                                            "filter": "true",
                                            "final": false,
                                            "functionInstanceId": "root:3:zikB",
                                            "id": "eval"
                                        }
                                    ]
                                },
                                "id": "root"
                            },
                            "Coordinated": {
                                ...
                            },
                            "Federated": {
                                ...
                            }
                        },
                        "referencedColumnPaths": [
                            [
                                "cnt"
                            ],
                            [
                                "srcaddr"
                            ]
                        ],
                        "searchTerms": [],
                        "useFormattedVisualization": true
                    },
                    "searchVersionByDatasetId": {},
                    "status": "completed",
                    "subQueryText": "dataset=\"cribl_search_sample\" | summarize cnt=count() by srcaddr"
                }
            ],
            "status": "completed",
            "timeCompleted": 1777446992662,
            "timeCreated": 1777446985069,
            "timeStarted": 1777446985598,
            "type": "standard",
            "user": "EXAMPLECLIENTID0000000000000000@clients",
            "userDetails": {
                "apiCredential": {
                    "clientId": "EXAMPLECLIENTID0000000000000000@clients",
                    "createdBy": "example.user@example.com",
                    "name": "example.user@example.com"
                },
                "displayUsername": "example.user@example.com",
                "email": "example.user@example.com",
                "roles": [
                    "search_user",
                    "org_user",
                    "ws_user"
                ],
                "ssoGroups": [],
                "type": "apiCredential",
                "username": "EXAMPLECLIENTID0000000000000000@clients"
            }
        }
    }
}

Human Readable Output

The job 1777446985069.KSZQ5h has been successfully updated.

cribl-search-job-delete


Deletes a specific search job.

Base Command

cribl-search-job-delete

Input

Argument Name Description Required
job_id The unique identifier of the search job to delete. Required

Context Output

There is no context output for this command.

Command example

!cribl-search-job-delete job_id="1777446985069.KSZQ5h"

Human Readable Output

The job 1777446985069.KSZQ5h has been successfully deleted.

cribl-search-dataset-list


Retrieves a list of available datasets or details of a specific dataset.

Base Command

cribl-search-dataset-list

Input

Argument Name Description Required
dataset_id The unique identifier of a specific dataset to retrieve. Optional
limit The maximum number of results to return. Default is 10. Optional
all_results Whether to return all results. If true, overrides the limit argument. Optional

Context Output

Path Type Description
Cribl.SearchDataset.id String The unique identifier of the dataset.
Cribl.SearchDataset.type String The dataset provider type (for example, s3, azure_blob, gcs).
Cribl.SearchDataset.provider String The identifier of the provider configuration backing the dataset.
Cribl.SearchDataset.region String The cloud region where the dataset’s underlying storage resides (when applicable).
Cribl.SearchDataset.bucket String The bucket/path template that locates the dataset’s underlying objects.
Cribl.SearchDataset.description String The human-readable description of the dataset.
Cribl.SearchDataset.filter String The filter expression applied to events from the dataset; defaults to “true” (passes all events).
Cribl.SearchDataset.tags Unknown The tags assigned to the dataset (string or array of strings).
Cribl.SearchDataset.breakerRulesets Unknown The event breaker rulesets associated with the dataset.
Cribl.SearchDataset.storageClasses Unknown The storage classes the dataset is configured to read from.
Cribl.SearchDataset.staleChannelFlushMs Number The time in milliseconds after which a stale channel is flushed during ingestion.

Command example

!cribl-search-dataset-list limit=3

Context Example

{
    "Cribl": {
        "SearchDataset": {
            "breakerRulesets": [
                "AWS Datatypes",
                "Apache Datatypes",
                "Syslog Datatypes",
                "Cribl Search",
                "Microsoft Windows Datatypes",
                "Azure Datatypes",
                "Microsoft O365 Datatypes",
                "Microsoft Graph API Datatypes"
            ],
            "bucket": "cribl-search-example/data/${dataSource}/${_time:%Y}/${_time:%m}/${_time:%d}/${_time:%H}",
            "description": "Search Cribl provided public sample data",
            "filter": "true",
            "id": "cribl_search_sample",
            "provider": "cribl_s3sample_provider",
            "region": "us-west-2",
            "staleChannelFlushMs": 10000,
            "storageClasses": [
                "STANDARD",
                "INTELLIGEN",
                "STANDARD_I",
                "ONEZONE_IA",
                "GLACIER_IR",
                "REDUCED_RE",
                "_RESTORED"
            ],
            "tags": "cribl:default",
            "type": "s3"
        }
    }
}

Human Readable Output

Datasets List

ID Provider Type Region
cribl_search_sample cribl_s3sample_provider s3 us-west-2

cribl-saved-search-list


Retrieves a list of saved searches or details of a specific saved search.

Base Command

cribl-saved-search-list

Input

Argument Name Description Required
search_id The unique identifier of a specific saved search to retrieve. Optional
limit The maximum number of results to return. Default is 10. Optional
all_results Whether to return all results. If true, overrides the limit argument. Optional

Context Output

Path Type Description
Cribl.SavedSearch.id String The unique identifier of the saved search.
Cribl.SavedSearch.name String The display name of the saved search.
Cribl.SavedSearch.query String The search query string defined by the saved search.

Command example

!cribl-saved-search-list limit=3

Context Example

{
    "Cribl": {
        "SavedSearch": [
            {
                "description": "Searches finished in the last 1h",
                "earliest": "-1h",
                "id": "cribl_search_finished_1h",
                "latest": "now",
                "lib": "cribl",
                "name": "cribl_search_finished_1h",
                "query": "cribl dataset=\"cribl_internal_logs\" source=*searches.log message=\"search finished\" | summarize count(), elapsedMS=sum(stats.elapsedMs), eventsFound=sum(stats.eventsFound) by user=coalesce(stats.userDisplayName, stats.user)"
            },
            {
                "description": "Searches started in the last 1h",
                "earliest": "-1h",
                "id": "cribl_search_started_1h",
                "latest": "now",
                "lib": "cribl",
                "name": "cribl_search_started_1h",
                "query": "cribl dataset=\"cribl_internal_logs\" source=*searches.log message=\"search started\" | summarize count() by user=coalesce(stats.userDisplayName, stats.user)"
            }
        ]
    }
}

Human Readable Output

Saved Searches List

ID Description Name Query
cribl_search_finished_1h Searches finished in the last 1h cribl_search_finished_1h cribl dataset=”cribl_internal_logs” source=*searches.log message=”search finished” | summarize count(), elapsedMS=sum(stats.elapsedMs), eventsFound=sum(stats.eventsFound) by user=coalesce(stats.userDisplayName, stats.user)
cribl_search_started_1h Searches started in the last 1h cribl_search_started_1h cribl dataset=”cribl_internal_logs” source=*searches.log message=”search started” | summarize count() by user=coalesce(stats.userDisplayName, stats.user)

Configuration parameters

  • url — Base URL (required)
  • credentials — Client ID (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (9)

  • cribl-saved-search-list

    Retrieves a list of saved searches or details of a specific saved search.

  • cribl-search-dataset-list

    Retrieves a list of available datasets or details of a specific dataset.

  • cribl-search-job-create

    Creates a new search job in Cribl Search.

  • cribl-search-job-delete

    Deletes a specific search job.

  • cribl-search-job-list

    Retrieves a list of search jobs or details of a specific search job.

  • cribl-search-job-update

    Updates a search job's status or privacy setting. At least one of status or is_private must be provided.

  • cribl-search-query

    Runs a search query against Cribl Search and returns results.

  • cribl-search-result

    Retrieves the results of a completed search job.

  • cribl-search-status

    Retrieves the status of a specific search job.

import demistomock as demisto
import pytest
from pytest_mock import MockerFixture
import json
from pathlib import Path
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from CriblSearch import CriblSearchClient


def load_mock_response(file_path: str) -> dict | list:
    """
    Helper function to load mock response data from a JSON file.

    Args:
        file_path (str): The relative path under the test_data directory to the JSON file
            (e.g. "cribl-search-dataset-list/list.json").

    Returns:
        dict | list: The parsed JSON content.
    """
    with (Path(__file__).parent / "test_data" / file_path).open() as f:
        return json.load(f)


@pytest.fixture(autouse=True)
def mock_support_multithreading(mocker: MockerFixture) -> None:
    """Mock support_multithreading to prevent demistomock attribute errors.

    This fixture automatically runs before each test to mock the support_multithreading
    function which is called during ContentClient initialization. Without this mock,
    tests fail with: AttributeError: module 'demistomock' has no attribute '_Demisto__do'
    """
    mocker.patch("ContentClientApiModule.support_multithreading")


@pytest.fixture
def client() -> "CriblSearchClient":
    """
    Pytest fixture that initializes and returns a CriblSearchClient instance for testing.

    Returns:
        CriblSearchClient: An instance of the Cribl Search API client.
    """
    from pydantic import SecretStr
    from CriblSearch import CriblSearchClient, CriblSearchParams, Credentials

    params = CriblSearchParams(
        url="https://main-test-org.cribl.cloud",  # type: ignore[arg-type]
        credentials=Credentials(
            identifier="test-client-id",
            password=SecretStr("test-client-secret"),
        ),
    )
    return CriblSearchClient(params)


# region helpers


@pytest.mark.parametrize(
    "results, limit, all_results, expected",
    [
        pytest.param([1, 2, 3, 4, 5], 2, True, [1, 2, 3, 4, 5], id="all_results_true"),
        pytest.param([1, 2, 3, 4, 5], 2, False, [1, 2], id="all_results_false_exceeds_limit"),
        pytest.param([1, 2, 3], 5, False, [1, 2, 3], id="all_results_false_within_limit"),
        pytest.param([], 5, False, [], id="empty_list"),
    ],
)
def test_truncate_results(results: list, limit: int | None, all_results: bool, expected: list) -> None:
    """
    Given:
        - A list of results, a limit, and a flag to return all results.
    When:
        - Calling the truncate_results helper function.
    Then:
        - Assert the list is truncated correctly based on the limit and all_results flag.
    """
    from CriblSearch import truncate_results

    assert truncate_results(results, limit, all_results) == expected


def test_parse_ndjson_empty() -> None:
    """
    Given:
        - An empty string.
    When:
        - Calling _parse_ndjson.
    Then:
        - Returns an empty dict.
    """
    from CriblSearch import _parse_ndjson

    assert _parse_ndjson("") == {}


def test_parse_ndjson_bad_first_line() -> None:
    """
    Given:
        - A string whose first (and only) line is not valid JSON.
    When:
        - Calling _parse_ndjson.
    Then:
        - Raises DemistoException with a Cribl-specific metadata-parse message,
          because the first line is parsed as the metadata.
    """
    from CriblSearch import _parse_ndjson, DemistoException

    with pytest.raises(DemistoException, match="Failed to parse Cribl Search response metadata"):
        _parse_ndjson("not-json\n")


def test_parse_ndjson_skips_bad_event_lines() -> None:
    """
    Given:
        - An ndjson string with a valid metadata line, two valid event lines, and one bad line.
    When:
        - Calling _parse_ndjson.
    Then:
        - The metadata is parsed, the bad event line is skipped, and only valid events are returned.
    """
    from CriblSearch import _parse_ndjson

    text = '{"job":{"id":"x"},"events":[]}\n{"event":1}\nnot-json\n{"event":2}\n'
    result = _parse_ndjson(text)

    assert result["job"] == {"id": "x"}
    assert result["events"] == [{"event": 1}, {"event": 2}]


# endregion

# region parameters


class TestCredentials:
    """Tests for the Credentials pydantic model."""

    @pytest.mark.parametrize(
        "password",
        [
            pytest.param("simple-key", id="simple_key"),
            pytest.param("P@ssw0rd!#$%", id="complex_password"),
            pytest.param("a" * 64, id="long_key"),
        ],
    )
    def test_password_stored_as_secret_str(self, password: str) -> None:
        """
        Given:
            - A plain string password.
        When:
            - Constructing a Credentials model.
        Then:
            - The password field is stored as SecretStr and accessible via get_secret_value().
        """
        from pydantic import SecretStr
        from CriblSearch import Credentials

        creds = Credentials(identifier="test-id", password=password)  # type: ignore[arg-type]

        assert isinstance(creds.password, SecretStr)
        assert creds.password.get_secret_value() == password

    def test_identifier_field(self) -> None:
        """
        Given:
            - An identifier string.
        When:
            - Constructing a Credentials model.
        Then:
            - The identifier field is set correctly.
        """
        from pydantic import SecretStr
        from CriblSearch import Credentials

        creds = Credentials(identifier="my-client-id", password=SecretStr("secret"))

        assert creds.identifier == "my-client-id"

    def test_credentials_missing_password(self) -> None:
        """
        Given:
            - No password provided.
        When:
            - Initializing Credentials model.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import Credentials

        with pytest.raises(DemistoException, match="password"):
            Credentials(identifier="test-id")  # type: ignore[call-arg]


class TestCriblSearchParams:
    """Tests for the CriblSearchParams pydantic model."""

    def test_client_id_property_returns_credentials_identifier(self) -> None:
        """
        Given:
            - Valid URL and credentials.
        When:
            - Constructing CriblSearchParams and accessing client_id.
        Then:
            - client_id returns the identifier from credentials.
        """
        from pydantic import SecretStr
        from CriblSearch import CriblSearchParams, Credentials

        params = CriblSearchParams(
            url="https://example.cribl.cloud",  # type: ignore[arg-type]
            credentials=Credentials(
                identifier="my-client-id",
                password=SecretStr("my-secret"),
            ),
        )

        assert params.client_id == "my-client-id"

    def test_client_secret_property_returns_credentials_password(self) -> None:
        """
        Given:
            - Valid URL and credentials.
        When:
            - Constructing CriblSearchParams and accessing client_secret.
        Then:
            - client_secret returns the SecretStr password from credentials.
        """
        from pydantic import SecretStr
        from CriblSearch import CriblSearchParams, Credentials

        params = CriblSearchParams(
            url="https://example.cribl.cloud",  # type: ignore[arg-type]
            credentials=Credentials(
                identifier="my-client-id",
                password=SecretStr("my-secret"),
            ),
        )

        assert isinstance(params.client_secret, SecretStr)
        assert params.client_secret.get_secret_value() == "my-secret"

    @pytest.mark.parametrize(
        "insecure, expected_verify",
        [
            pytest.param(False, True, id="insecure_false_verify_true"),
            pytest.param(True, False, id="insecure_true_verify_false"),
        ],
    )
    def test_verify_property(self, insecure: bool, expected_verify: bool) -> None:
        """
        Given:
            - CriblSearchParams with various insecure values.
        When:
            - Accessing the verify property.
        Then:
            - verify is the logical inverse of insecure.
        """
        from pydantic import SecretStr
        from CriblSearch import CriblSearchParams, Credentials

        params = CriblSearchParams(
            url="https://example.cribl.cloud",  # type: ignore[arg-type]
            credentials=Credentials(
                identifier="test-id",
                password=SecretStr("key"),
            ),
            insecure=insecure,
        )

        assert params.verify is expected_verify

    def test_url_accepted(self) -> None:
        """
        Given:
            - A valid URL string.
        When:
            - Constructing CriblSearchParams.
        Then:
            - The url field is set and contains the expected host.
        """
        from urllib.parse import urlparse
        from pydantic import SecretStr
        from CriblSearch import CriblSearchParams, Credentials

        params = CriblSearchParams(
            url="https://main-test-org.cribl.cloud",  # type: ignore[arg-type]
            credentials=Credentials(
                identifier="test-id",
                password=SecretStr("key"),
            ),
        )

        assert urlparse(str(params.url)).hostname == "main-test-org.cribl.cloud"

    def test_params_missing_url(self) -> None:
        """
        Given:
            - No URL provided.
        When:
            - Initializing CriblSearchParams.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import CriblSearchParams

        with pytest.raises(DemistoException, match="url"):
            CriblSearchParams(
                credentials={"identifier": "test-id", "password": "secret"},  # type: ignore[call-arg]
            )

    def test_params_missing_credentials(self) -> None:
        """
        Given:
            - No credentials provided.
        When:
            - Initializing CriblSearchParams.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import CriblSearchParams

        with pytest.raises(DemistoException, match="credentials"):
            CriblSearchParams(url="https://example.cribl.cloud")  # type: ignore[call-arg]


# endregion

# region test-module


def test_module_authentication_error(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - Client that raises ContentClientAuthenticationError.
    When:
        - Running test_module.
    Then:
        - Assert appropriate error message is returned.
    """
    from ContentClientApiModule import ContentClientAuthenticationError
    from CriblSearch import test_module

    mocker.patch.object(client, "search_datasets_list", side_effect=ContentClientAuthenticationError("Unauthorized"))
    # test_module logs the traceback via demisto.error on failure; patch it to keep stdout clean.
    mocker.patch.object(demisto, "error")

    result = test_module(client)

    assert result.startswith("AuthenticationError: Connection failed.")
    assert "Unauthorized" in result


# endregion

# region cribl-search-query


class TestSearchQueryArgs:
    """Tests for the SearchQueryArgs pydantic model."""

    def test_defaults(self) -> None:
        """
        Given:
            - Only the minimum required combo field provided (query_id) so the root validator passes.
        When:
            - Constructing SearchQueryArgs with defaults.
        Then:
            - limit defaults to 50, force defaults to False, and all other optional fields default to None.
        """
        from CriblSearch import SearchQueryArgs

        args = SearchQueryArgs(query_id="some-id")  # type: ignore[call-arg]

        assert args.limit == 50
        assert args.force is False
        assert args.query_id == "some-id"
        assert args.job_id is None
        assert args.query is None
        assert args.earliest is None
        assert args.latest is None
        assert args.sample_rate is None
        assert args.page is None

    @pytest.mark.parametrize(
        "limit_input, expected_limit",
        [
            pytest.param(50, 50, id="int_default"),
            pytest.param("42", 42, id="string_number_coerced"),
            pytest.param(10, 10, id="int_explicit"),
            pytest.param(None, None, id="none_passes_through"),
        ],
    )
    def test_limit_coercion(self, limit_input: int | str | None, expected_limit: int | None) -> None:
        """
        Given:
            - Various limit inputs (int, string number, None).
        When:
            - Constructing SearchQueryArgs (with query_id to satisfy the root validator).
        Then:
            - limit is coerced to int via arg_to_number or left as None.
        """
        from CriblSearch import SearchQueryArgs

        args = SearchQueryArgs(query_id="x", limit=limit_input)  # type: ignore[call-arg]

        assert args.limit == expected_limit

    @pytest.mark.parametrize(
        "force_input, expected",
        [
            pytest.param(False, False, id="bool_false"),
            pytest.param(True, True, id="bool_true"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_force_coercion(self, force_input: bool | str, expected: bool) -> None:
        """
        Given:
            - Various force inputs (bool, string).
        When:
            - Constructing SearchQueryArgs (with query_id to satisfy the root validator).
        Then:
            - force is coerced to bool via argToBoolean.
        """
        from CriblSearch import SearchQueryArgs

        args = SearchQueryArgs(query_id="x", force=force_input)  # type: ignore[call-arg]

        assert args.force is expected

    def test_validator_requires_at_least_one_combo(self) -> None:
        """
        Given:
            - No combo field (query, query_id, or job_id) provided.
        When:
            - Constructing SearchQueryArgs.
        Then:
            - The root validator raises, wrapped by ContentBaseModel into DemistoException.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchQueryArgs

        with pytest.raises(DemistoException, match="At least one of"):
            SearchQueryArgs()  # type: ignore[call-arg]

    def test_validator_query_requires_earliest_and_latest(self) -> None:
        """
        Given:
            - Only `query` provided, without `earliest` or `latest`.
        When:
            - Constructing SearchQueryArgs.
        Then:
            - The root validator raises, wrapped by ContentBaseModel into DemistoException.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchQueryArgs

        with pytest.raises(DemistoException, match="earliest"):
            SearchQueryArgs(query="dataset=foo | limit 5")  # type: ignore[call-arg]


def test_search_query_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - Search query arguments.
    When:
        - Calling the search_query_command.
    Then:
        - Assert the client's search_query method is called.
        - Assert the response is correctly processed into CommandResults with expected outputs and prefix.
    """
    from CriblSearch import search_query_command, SearchQueryArgs

    mock_response = load_mock_response("cribl-search-query/response.json")
    mocker.patch.object(client, "search_query", return_value=mock_response)

    args = SearchQueryArgs(query='dataset="cribl_search_sample" | limit 5', earliest="-5m", latest="now")  # type: ignore[call-arg]

    response = search_query_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchQuery"
    assert response.outputs_key_field == "job.id"
    assert "Search Query - Job Info" in response.readable_output

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("isFinished") is False
    assert outputs.get("totalEventCount") == 0
    assert outputs.get("events") == []
    assert outputs.get("job", {}).get("id") == "1777383267244.i0hUyX"
    assert outputs.get("job", {}).get("status") == "queued"


# endregion

# region cribl-search-status


class TestSearchStatusArgs:
    """Tests for the SearchStatusArgs pydantic model."""

    def test_job_id_required(self) -> None:
        """
        Given:
            - A job_id string.
        When:
            - Constructing SearchStatusArgs.
        Then:
            - job_id is set correctly.
        """
        from CriblSearch import SearchStatusArgs

        args = SearchStatusArgs(job_id="job_abc123")

        assert args.job_id == "job_abc123"

    def test_job_id_missing_raises(self) -> None:
        """
        Given:
            - No job_id provided.
        When:
            - Constructing SearchStatusArgs.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchStatusArgs

        with pytest.raises(DemistoException, match="job_id"):
            SearchStatusArgs()  # type: ignore[call-arg]


def test_search_status_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A job ID to check status.
    When:
        - Calling the search_status_command.
    Then:
        - Assert the client's search_job_status method is called.
        - Assert the response is correctly processed into CommandResults with expected outputs.
    """
    from CriblSearch import search_status_command, SearchStatusArgs

    mock_response = load_mock_response("cribl-search-status/response.json")
    mocker.patch.object(client, "search_job_status", return_value=mock_response)

    args = SearchStatusArgs(job_id="1777207075060.cCPnVx")

    response = search_status_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchStatus"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("status") == "completed"
    assert outputs.get("timeStarted") == 1777207075512
    assert outputs.get("timeCompleted") == 1777207082580
    assert "Status" in response.readable_output


# endregion

# region cribl-search-result


class TestSearchResultArgs:
    """Tests for the SearchResultArgs pydantic model."""

    def test_defaults(self) -> None:
        """
        Given:
            - Only job_id provided.
        When:
            - Constructing SearchResultArgs with defaults.
        Then:
            - limit defaults to 50 and all_results defaults to False.
        """
        from CriblSearch import SearchResultArgs

        args = SearchResultArgs(job_id="job_abc123")  # type: ignore[call-arg]

        assert args.limit == 50
        assert args.all_results is False
        assert args.lower_bound is None
        assert args.upper_bound is None
        assert args.page is None

    @pytest.mark.parametrize(
        "limit_input, expected_limit",
        [
            pytest.param(50, 50, id="int_default"),
            pytest.param("25", 25, id="string_number_coerced"),
            pytest.param(None, None, id="none_passes_through"),
        ],
    )
    def test_limit_coercion(self, limit_input: int | str | None, expected_limit: int | None) -> None:
        """
        Given:
            - Various limit inputs (int, string number, None).
        When:
            - Constructing SearchResultArgs.
        Then:
            - limit is coerced to int via arg_to_number or left as None.
        """
        from CriblSearch import SearchResultArgs

        args = SearchResultArgs(job_id="job_abc123", limit=limit_input)  # type: ignore[call-arg]

        assert args.limit == expected_limit

    @pytest.mark.parametrize(
        "all_results_input, expected",
        [
            pytest.param(False, False, id="bool_false"),
            pytest.param(True, True, id="bool_true"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_all_results_coercion(self, all_results_input: bool | str, expected: bool) -> None:
        """
        Given:
            - Various all_results inputs (bool, string).
        When:
            - Constructing SearchResultArgs.
        Then:
            - all_results is coerced to bool via argToBoolean.
        """
        from CriblSearch import SearchResultArgs

        args = SearchResultArgs(job_id="job_abc123", all_results=all_results_input)  # type: ignore[call-arg]

        assert args.all_results is expected


def test_search_result_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A job ID to retrieve results.
    When:
        - Calling the search_result_command.
    Then:
        - Assert the client's search_job_results method is called.
        - Assert the response is correctly processed into CommandResults with expected outputs.
    """
    from CriblSearch import search_result_command, SearchResultArgs

    mock_response = load_mock_response("cribl-search-result/response.json")
    mocker.patch.object(client, "search_job_results", return_value=mock_response)

    args = SearchResultArgs(job_id="1777207075060.cCPnVx")  # type: ignore[call-arg]

    response = search_result_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchResult"
    assert response.outputs_key_field == "job.id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("isFinished") is True
    assert outputs.get("totalEventCount") == 33
    assert outputs.get("persistedEventCount") == 33
    assert outputs.get("job", {}).get("id") == "1777207075060.cCPnVx"
    assert outputs.get("job", {}).get("status") == "completed"

    events = outputs.get("events", [])
    assert len(events) == 5
    assert events[0].get("srcaddr") == "192.0.2.10"
    assert events[4].get("srcaddr") == "192.0.2.14"

    assert "Job Info" in response.readable_output


# endregion

# region cribl-search-job-create


class TestSearchJobCreateArgs:
    """Tests for the SearchJobCreateArgs pydantic model."""

    def test_required_query(self) -> None:
        """
        Given:
            - A query string.
        When:
            - Constructing SearchJobCreateArgs with only query.
        Then:
            - query is set and optional fields have defaults.
        """
        from CriblSearch import SearchJobCreateArgs

        args = SearchJobCreateArgs(query='dataset="cribl_search_sample"')  # type: ignore[call-arg]

        assert args.query == 'dataset="cribl_search_sample"'
        assert args.is_private is True
        assert args.earliest is None
        assert args.latest is None
        assert args.sample_rate is None
        assert args.num_events_before is None
        assert args.num_events_after is None
        assert args.target_event_time is None
        assert args.set_options is None
        assert args.expected_output_type is None

    @pytest.mark.parametrize(
        "is_private_input, expected",
        [
            pytest.param(True, True, id="bool_true"),
            pytest.param(False, False, id="bool_false"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_is_private_coercion(self, is_private_input: bool | str, expected: bool) -> None:
        """
        Given:
            - Various is_private inputs (bool, string).
        When:
            - Constructing SearchJobCreateArgs.
        Then:
            - is_private is coerced to bool via argToBoolean.
        """
        from CriblSearch import SearchJobCreateArgs

        args = SearchJobCreateArgs(query="test", is_private=is_private_input)  # type: ignore[call-arg]

        assert args.is_private is expected

    @pytest.mark.parametrize(
        "set_options_input, expected",
        [
            pytest.param('{"key": "val", "num": 42}', {"key": "val", "num": 42}, id="json_string_parsed"),
            pytest.param({"a": 1, "b": "two"}, {"a": 1, "b": "two"}, id="dict_passthrough"),
        ],
    )
    def test_set_options_json_coercion(self, set_options_input: str | dict, expected: dict) -> None:
        """
        Given:
            - set_options as a JSON string or dict.
        When:
            - Constructing SearchJobCreateArgs.
        Then:
            - set_options is parsed from JSON string or passed through as dict.
        """
        from CriblSearch import SearchJobCreateArgs

        args = SearchJobCreateArgs(query="test", set_options=set_options_input)  # type: ignore[call-arg]

        assert args.set_options == expected

    def test_set_options_invalid_json_raises(self) -> None:
        """
        Given:
            - set_options as an invalid JSON string.
        When:
            - Constructing SearchJobCreateArgs.
        Then:
            - DemistoException is raised because the invalid string fails Pydantic's dict type check.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchJobCreateArgs

        with pytest.raises(DemistoException, match="set_options"):
            SearchJobCreateArgs(query="test", set_options="not-json{broken")  # type: ignore[call-arg]

    def test_missing_query_raises(self) -> None:
        """
        Given:
            - No query provided.
        When:
            - Constructing SearchJobCreateArgs.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchJobCreateArgs

        with pytest.raises(DemistoException, match="query"):
            SearchJobCreateArgs()  # type: ignore[call-arg]


def test_search_job_create_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - Search job creation arguments.
    When:
        - Calling the search_job_create_command.
    Then:
        - Assert the client's search_job_create method is called.
        - Assert the response is correctly processed into CommandResults with expected job details.
    """
    from CriblSearch import search_job_create_command, SearchJobCreateArgs

    mock_response = load_mock_response("cribl-search-job-create/response.json")
    mocker.patch.object(client, "search_job_create", return_value=mock_response)

    args = SearchJobCreateArgs(query='dataset="cribl_search_sample" | limit 5')  # type: ignore[call-arg]

    response = search_job_create_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchJob"
    assert response.outputs_key_field == "id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("id") == "1777381220097.F0vzpw"
    assert outputs.get("status") == "queued"
    assert outputs.get("user") == "EXAMPLECLIENTID0000000000000000@clients"
    assert outputs.get("isPrivate") is True
    assert "Search Job Created" in response.readable_output


# endregion

# region cribl-search-job-list


class TestSearchJobListArgs:
    """Tests for the SearchJobListArgs pydantic model."""

    def test_defaults(self) -> None:
        """
        Given:
            - No arguments provided.
        When:
            - Constructing SearchJobListArgs with defaults.
        Then:
            - limit defaults to 10, all_results defaults to False, job_id defaults to None.
        """
        from CriblSearch import SearchJobListArgs

        args = SearchJobListArgs()  # type: ignore[call-arg]

        assert args.limit == 10
        assert args.all_results is False
        assert args.job_id is None

    def test_job_id_optional(self) -> None:
        """
        Given:
            - A job_id string.
        When:
            - Constructing SearchJobListArgs with job_id.
        Then:
            - job_id is set correctly.
        """
        from CriblSearch import SearchJobListArgs

        args = SearchJobListArgs(job_id="job_abc123")  # type: ignore[call-arg]

        assert args.job_id == "job_abc123"

    @pytest.mark.parametrize(
        "limit_input, expected_limit",
        [
            pytest.param(10, 10, id="int_default"),
            pytest.param("5", 5, id="string_number_coerced"),
            pytest.param(None, None, id="none_passes_through"),
        ],
    )
    def test_limit_coercion(self, limit_input: int | str | None, expected_limit: int | None) -> None:
        """
        Given:
            - Various limit inputs.
        When:
            - Constructing SearchJobListArgs.
        Then:
            - limit is coerced to int via arg_to_number or left as None.
        """
        from CriblSearch import SearchJobListArgs

        args = SearchJobListArgs(limit=limit_input)  # type: ignore[call-arg]

        assert args.limit == expected_limit


def test_search_job_list_command_all(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - No specific job ID (requesting all jobs).
    When:
        - Calling the search_job_list_command.
    Then:
        - Assert the client's search_jobs_list method is called.
        - Assert the response is correctly processed into CommandResults containing a list of jobs.
    """
    from CriblSearch import search_job_list_command, SearchJobListArgs

    mock_response = load_mock_response("cribl-search-job-list/list.json")
    mocker.patch.object(client, "search_jobs_list", return_value=mock_response)

    args = SearchJobListArgs(job_id=None, limit=None, all_results=False)

    response = search_job_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchJob"
    assert response.outputs_key_field == "id"

    outputs: list[dict[str, Any]] = response.outputs  # type: ignore[assignment]

    assert len(outputs) == 3
    assert outputs[0].get("id") == "1777207075060.cCPnVx"
    assert outputs[1].get("id") == "1777207943198.pb0ZZ0"
    assert "Search Jobs List" in response.readable_output


def test_search_job_list_command_single(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A specific job ID.
    When:
        - Calling the search_job_list_command.
    Then:
        - Assert the client's search_jobs_list method is called for the specific job.
        - Assert the response is correctly processed into CommandResults with detailed job information.
    """
    from CriblSearch import search_job_list_command, SearchJobListArgs

    mock_response = load_mock_response("cribl-search-job-list/get.json")
    mocker.patch.object(client, "search_jobs_list", return_value=mock_response)

    args = SearchJobListArgs(job_id="1777207075060.cCPnVx", limit=None, all_results=False)

    response = search_job_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchJob"
    assert response.outputs_key_field == "id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("id") == "1777207075060.cCPnVx"
    assert outputs.get("status") == "completed"
    assert outputs.get("user") == "EXAMPLECLIENTID0000000000000000@clients"
    assert "Search Job 1777207075060.cCPnVx Details" in response.readable_output


# endregion

# region cribl-search-job-update


class TestSearchJobUpdateArgs:
    """Tests for the SearchJobUpdateArgs pydantic model."""

    def test_job_id_required(self) -> None:
        """
        Given:
            - A job_id and status.
        When:
            - Constructing SearchJobUpdateArgs.
        Then:
            - job_id and status are set correctly.
        """
        from CriblSearch import SearchJobUpdateArgs

        args = SearchJobUpdateArgs(job_id="job_abc123", status="canceled")  # type: ignore[call-arg]

        assert args.job_id == "job_abc123"
        assert args.status == "canceled"
        assert args.is_private is None

    @pytest.mark.parametrize(
        "is_private_input, expected",
        [
            pytest.param(None, None, id="none_passthrough"),
            pytest.param(True, True, id="bool_true"),
            pytest.param(False, False, id="bool_false"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_is_private_coercion(self, is_private_input: bool | str | None, expected: bool | None) -> None:
        """
        Given:
            - Various is_private inputs.
        When:
            - Constructing SearchJobUpdateArgs (with status to satisfy the root validator).
        Then:
            - is_private is coerced via argToBoolean or left as None.
        """
        from CriblSearch import SearchJobUpdateArgs

        args = SearchJobUpdateArgs(
            job_id="job_abc123",
            status="canceled",
            is_private=is_private_input,  # type: ignore[arg-type]
        )

        assert args.is_private is expected

    def test_job_id_missing_raises(self) -> None:
        """
        Given:
            - No job_id provided (status is provided so the at-least-one validator passes).
        When:
            - Constructing SearchJobUpdateArgs.
        Then:
            - Assert DemistoException is raised for the missing job_id.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchJobUpdateArgs

        with pytest.raises(DemistoException, match="job_id"):
            SearchJobUpdateArgs(status="canceled")  # type: ignore[call-arg]

    def test_validator_requires_status_or_is_private(self) -> None:
        """
        Given:
            - A job_id but neither status nor is_private.
        When:
            - Constructing SearchJobUpdateArgs.
        Then:
            - The root validator raises, wrapped by ContentBaseModel into DemistoException.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchJobUpdateArgs

        with pytest.raises(DemistoException, match="At least one of 'status' or 'is_private'"):
            SearchJobUpdateArgs(job_id="job_abc123")  # type: ignore[call-arg]


def test_search_job_update_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A job ID and is_private flag to update.
    When:
        - Calling the search_job_update_command.
    Then:
        - Assert the client's search_job_update method is called.
        - Assert the readable output indicates successful update.
    """
    from CriblSearch import search_job_update_command, SearchJobUpdateArgs

    mock_response = load_mock_response("cribl-search-job-update/response.json")
    mocker.patch.object(client, "search_job_update", return_value=mock_response)

    args = SearchJobUpdateArgs(job_id="1777207075060.cCPnVx", is_private=False)  # type: ignore[call-arg]

    response = search_job_update_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchJob"
    assert response.outputs_key_field == "id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("id") == "1777207075060.cCPnVx"
    assert outputs.get("isPrivate") is False
    assert "successfully updated" in response.readable_output


# endregion

# region cribl-search-job-delete


class TestSearchJobDeleteArgs:
    """Tests for the SearchJobDeleteArgs pydantic model."""

    def test_job_id_required(self) -> None:
        """
        Given:
            - A job_id string.
        When:
            - Constructing SearchJobDeleteArgs.
        Then:
            - job_id is set correctly.
        """
        from CriblSearch import SearchJobDeleteArgs

        args = SearchJobDeleteArgs(job_id="job_abc123")

        assert args.job_id == "job_abc123"

    def test_job_id_missing_raises(self) -> None:
        """
        Given:
            - No job_id provided.
        When:
            - Constructing SearchJobDeleteArgs.
        Then:
            - Assert DemistoException is raised.
        """
        from CommonServerPython import DemistoException
        from CriblSearch import SearchJobDeleteArgs

        with pytest.raises(DemistoException, match="job_id"):
            SearchJobDeleteArgs()  # type: ignore[call-arg]


def test_search_job_delete_command(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A job ID to delete.
    When:
        - Calling the search_job_delete_command.
    Then:
        - Assert the client's search_job_delete method is called with the correct job ID.
        - Assert the readable output indicates successful deletion.
        - Assert no outputs are returned.
    """
    from CriblSearch import search_job_delete_command, SearchJobDeleteArgs

    mocker.patch.object(client, "search_job_delete", return_value=None)

    args = SearchJobDeleteArgs(job_id="job_abc123")

    response = search_job_delete_command(client, args)

    assert "successfully deleted" in response.readable_output
    assert response.outputs is None
    client.search_job_delete.assert_called_once_with(job_id="job_abc123")  # type: ignore[attr-defined]


# endregion

# region cribl-search-dataset-list


class TestSearchDatasetListArgs:
    """Tests for the SearchDatasetListArgs pydantic model."""

    def test_defaults(self) -> None:
        """
        Given:
            - No arguments provided.
        When:
            - Constructing SearchDatasetListArgs with defaults.
        Then:
            - limit defaults to 10, all_results defaults to False, dataset_id defaults to None.
        """
        from CriblSearch import SearchDatasetListArgs

        args = SearchDatasetListArgs()  # type: ignore[call-arg]

        assert args.limit == 10
        assert args.all_results is False
        assert args.dataset_id is None

    @pytest.mark.parametrize(
        "limit_input, expected_limit",
        [
            pytest.param(10, 10, id="int_default"),
            pytest.param("5", 5, id="string_number_coerced"),
            pytest.param(None, None, id="none_passes_through"),
        ],
    )
    def test_limit_coercion(self, limit_input: int | str | None, expected_limit: int | None) -> None:
        """
        Given:
            - Various limit inputs.
        When:
            - Constructing SearchDatasetListArgs.
        Then:
            - limit is coerced to int via arg_to_number or left as None.
        """
        from CriblSearch import SearchDatasetListArgs

        args = SearchDatasetListArgs(limit=limit_input)  # type: ignore[call-arg]

        assert args.limit == expected_limit

    @pytest.mark.parametrize(
        "all_results_input, expected",
        [
            pytest.param(False, False, id="bool_false"),
            pytest.param(True, True, id="bool_true"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_all_results_coercion(self, all_results_input: bool | str, expected: bool) -> None:
        """
        Given:
            - Various all_results inputs (bool, string).
        When:
            - Constructing SearchDatasetListArgs.
        Then:
            - all_results is coerced to bool via argToBoolean.
        """
        from CriblSearch import SearchDatasetListArgs

        args = SearchDatasetListArgs(all_results=all_results_input)  # type: ignore[call-arg]

        assert args.all_results is expected


def test_search_dataset_list_command_all(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - No specific dataset ID (requesting all datasets).
    When:
        - Calling the search_dataset_list_command.
    Then:
        - Assert the client's search_datasets_list method is called.
        - Assert the response is correctly processed into CommandResults containing a list of datasets.
    """
    from CriblSearch import search_dataset_list_command, SearchDatasetListArgs

    mock_response = load_mock_response("cribl-search-dataset-list/list.json")
    mocker.patch.object(client, "search_datasets_list", return_value=mock_response)

    args = SearchDatasetListArgs(dataset_id=None, limit=None, all_results=False)

    response = search_dataset_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchDataset"
    assert response.outputs_key_field == "id"

    outputs: list[dict[str, Any]] = response.outputs  # type: ignore[assignment]

    assert len(outputs) == 1
    assert outputs[0].get("id") == "cribl_search_sample"
    assert outputs[0].get("type") == "s3"
    assert "Datasets List" in response.readable_output


def test_search_dataset_list_command_single(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A specific dataset ID.
    When:
        - Calling the search_dataset_list_command.
    Then:
        - Assert the client's search_datasets_list method is called for the specific dataset.
        - Assert the response is correctly processed into CommandResults with detailed dataset information.
    """
    from CriblSearch import search_dataset_list_command, SearchDatasetListArgs

    mock_response = load_mock_response("cribl-search-dataset-list/get.json")
    mocker.patch.object(client, "search_datasets_list", return_value=mock_response)

    args = SearchDatasetListArgs(dataset_id="cribl_search_sample", limit=None, all_results=False)

    response = search_dataset_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SearchDataset"
    assert response.outputs_key_field == "id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("id") == "cribl_search_sample"
    assert outputs.get("type") == "s3"
    assert outputs.get("provider") == "cribl_s3sample_provider"
    assert "Dataset cribl_search_sample Details" in response.readable_output


# endregion

# region cribl-saved-search-list


class TestSavedSearchListArgs:
    """Tests for the SavedSearchListArgs pydantic model."""

    def test_defaults(self) -> None:
        """
        Given:
            - No arguments provided.
        When:
            - Constructing SavedSearchListArgs with defaults.
        Then:
            - limit defaults to 10, all_results defaults to False, search_id defaults to None.
        """
        from CriblSearch import SavedSearchListArgs

        args = SavedSearchListArgs()  # type: ignore[call-arg]

        assert args.limit == 10
        assert args.all_results is False
        assert args.search_id is None

    @pytest.mark.parametrize(
        "limit_input, expected_limit",
        [
            pytest.param(10, 10, id="int_default"),
            pytest.param("5", 5, id="string_number_coerced"),
            pytest.param(None, None, id="none_passes_through"),
        ],
    )
    def test_limit_coercion(self, limit_input: int | str | None, expected_limit: int | None) -> None:
        """
        Given:
            - Various limit inputs.
        When:
            - Constructing SavedSearchListArgs.
        Then:
            - limit is coerced to int via arg_to_number or left as None.
        """
        from CriblSearch import SavedSearchListArgs

        args = SavedSearchListArgs(limit=limit_input)  # type: ignore[call-arg]

        assert args.limit == expected_limit

    @pytest.mark.parametrize(
        "all_results_input, expected",
        [
            pytest.param(False, False, id="bool_false"),
            pytest.param(True, True, id="bool_true"),
            pytest.param("true", True, id="string_true"),
            pytest.param("false", False, id="string_false"),
        ],
    )
    def test_all_results_coercion(self, all_results_input: bool | str, expected: bool) -> None:
        """
        Given:
            - Various all_results inputs (bool, string).
        When:
            - Constructing SavedSearchListArgs.
        Then:
            - all_results is coerced to bool via argToBoolean.
        """
        from CriblSearch import SavedSearchListArgs

        args = SavedSearchListArgs(all_results=all_results_input)  # type: ignore[call-arg]

        assert args.all_results is expected


def test_saved_search_list_command_all(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - No specific search ID (requesting all saved searches).
    When:
        - Calling the saved_search_list_command.
    Then:
        - Assert the client's saved_searches_list method is called.
        - Assert the response is correctly processed into CommandResults containing a list of saved searches.
    """
    from CriblSearch import saved_search_list_command, SavedSearchListArgs

    mock_response = load_mock_response("cribl-saved-search-list/list.json")
    mocker.patch.object(client, "saved_searches_list", return_value=mock_response)

    args = SavedSearchListArgs(search_id=None, limit=None, all_results=False)

    response = saved_search_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SavedSearch"
    assert response.outputs_key_field == "id"

    outputs: list[dict[str, Any]] = response.outputs  # type: ignore[assignment]

    assert len(outputs) == 2
    assert outputs[0].get("id") == "cribl_search_finished_1h"
    assert outputs[1].get("id") == "cribl_search_started_1h"
    assert "Saved Searches List" in response.readable_output


def test_saved_search_list_command_single(mocker: MockerFixture, client: "CriblSearchClient") -> None:
    """
    Given:
        - A specific search ID.
    When:
        - Calling the saved_search_list_command.
    Then:
        - Assert the client's saved_searches_list method is called for the specific saved search.
        - Assert the response is correctly processed into CommandResults with detailed saved search information.
    """
    from CriblSearch import saved_search_list_command, SavedSearchListArgs

    mock_response = load_mock_response("cribl-saved-search-list/get.json")
    mocker.patch.object(client, "saved_searches_list", return_value=mock_response)

    args = SavedSearchListArgs(search_id="cribl_search_finished_1h", limit=None, all_results=False)

    response = saved_search_list_command(client, args)

    assert response.outputs_prefix == "Cribl.SavedSearch"
    assert response.outputs_key_field == "id"

    outputs: dict[str, Any] = response.outputs  # type: ignore[assignment]

    assert outputs.get("id") == "cribl_search_finished_1h"
    assert outputs.get("name") == "cribl_search_finished_1h"
    assert outputs.get("description") == "Searches finished in the last 1h"
    assert "Saved Search cribl_search_finished_1h Details" in response.readable_output


# endregion