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
| ID | CriblSearch |
|---|---|
| Provider | Cribl |
| Category | IT Services |
| From Version | 6.10.0 |
| Docker Image | demisto/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-listRetrieves a list of saved searches or details of a specific saved search.
-
cribl-search-dataset-listRetrieves a list of available datasets or details of a specific dataset.
-
cribl-search-job-createCreates a new search job in Cribl Search.
-
cribl-search-job-deleteDeletes a specific search job.
-
cribl-search-job-listRetrieves a list of search jobs or details of a specific search job.
-
cribl-search-job-updateUpdates a search job's status or privacy setting. At least one of status or is_private must be provided.
-
cribl-search-queryRuns a search query against Cribl Search and returns results.
-
cribl-search-resultRetrieves the results of a completed search job.
-
cribl-search-statusRetrieves 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