Skip to content

Commit b34238f

Browse files
committed
chore: minor refactoring and improvements
1 parent 1c34c95 commit b34238f

9 files changed

Lines changed: 159 additions & 14 deletions

File tree

src/secops/chronicle/client.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,21 +1626,26 @@ def list_feeds(
16261626
page_size: int = 100,
16271627
page_token: str = None,
16281628
api_version: APIVersion | None = None,
1629-
) -> list[dict[str, Any]]:
1629+
as_list: bool = True,
1630+
) -> dict[str, Any] | list[dict[str, Any]]:
16301631
"""List feeds.
16311632
16321633
Args:
16331634
page_size: The maximum number of feeds to return
16341635
page_token: A page token, received from a previous ListFeeds call
16351636
api_version: (Optional) Preferred API version to use.
1637+
as_list: If True, return only the list of feeds.
1638+
If False, return dict with metadata and pagination tokens.
1639+
Defaults to True for backward compatibility.
16361640
16371641
Returns:
1638-
List of feed dictionaries
1642+
If as_list is True: List of feed dictionaries.
1643+
If as_list is False: Dict with feeds list and pagination metadata.
16391644
16401645
Raises:
16411646
APIError: If the API request fails
16421647
"""
1643-
return _list_feeds(self, page_size, page_token, api_version)
1648+
return _list_feeds(self, page_size, page_token, api_version, as_list)
16441649

16451650
def get_feed(
16461651
self, feed_id: str, api_version: APIVersion | None = None
@@ -2085,7 +2090,8 @@ def list_rules(
20852090
page_size: int | None = None,
20862091
page_token: str | None = None,
20872092
api_version: APIVersion | None = APIVersion.V1,
2088-
) -> dict[str, Any]:
2093+
as_list: bool = False,
2094+
) -> dict[str, Any] | list[Any]:
20892095
"""Gets a list of rules.
20902096
20912097
Args:
@@ -2099,9 +2105,12 @@ def list_rules(
20992105
page_size: Maximum number of rules to return per page.
21002106
page_token: Token for the next page of results, if available.
21012107
api_version: (Optional) Preferred API version to use.
2108+
as_list: If True, return only the list of rules.
2109+
If False, return dict with metadata and pagination tokens.
21022110
21032111
Returns:
2104-
Dictionary containing information about rules
2112+
If as_list is True: List of rules.
2113+
If as_list is False: Dict with rules list and pagination metadata.
21052114
21062115
Raises:
21072116
APIError: If the API request fails
@@ -2112,6 +2121,7 @@ def list_rules(
21122121
page_size=page_size,
21132122
page_token=page_token,
21142123
api_version=api_version,
2124+
as_list=as_list,
21152125
)
21162126

21172127
def update_rule(
@@ -4852,17 +4862,22 @@ def list_rule_deployments(
48524862
page_token: str | None = None,
48534863
filter_query: str | None = None,
48544864
api_version: APIVersion | None = APIVersion.V1,
4855-
) -> dict[str, Any]:
4865+
as_list: bool = False,
4866+
) -> dict[str, Any] | list[Any]:
48564867
"""List rule deployments for the instance.
48574868
48584869
Args:
48594870
page_size: Maximum number of deployments to return per page
48604871
page_token: Token for the next page of results, if available
48614872
filter_query: Optional filter query to restrict results
48624873
api_version: (Optional) Preferred API version to use.
4874+
as_list: If True, return only the list of rule deployments.
4875+
If False, return dict with metadata and pagination tokens.
48634876
48644877
Returns:
4865-
Dictionary containing rule deployments and pagination info
4878+
If as_list is True: List of rule deployments.
4879+
If as_list is False: Dict with ruleDeployments list and
4880+
pagination metadata.
48664881
48674882
Raises:
48684883
APIError: If the API request fails
@@ -4873,6 +4888,7 @@ def list_rule_deployments(
48734888
page_token=page_token,
48744889
filter_query=filter_query,
48754890
api_version=api_version,
4891+
as_list=as_list,
48764892
)
48774893

48784894
def set_rule_alerting(

src/secops/chronicle/rule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def search_rules(
319319
except re.error as e:
320320
raise SecOpsError(f"Invalid regular expression: {query}") from e
321321

322-
rules = list_rules(client, api_version=api_version)
322+
rules = list_rules(client, api_version=api_version, as_list=False)
323323
results = {"rules": []}
324324
for rule in rules["rules"]:
325325
rule_text = rule.get("text", "")

src/secops/cli/commands/feed.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import sys
1818

19+
from secops.cli.utils.common_args import add_as_list_arg, add_pagination_args
1920
from secops.cli.utils.formatters import output_formatter
2021

2122

@@ -29,6 +30,8 @@ def setup_feed_command(subparsers):
2930

3031
# List feeds command
3132
list_parser = feed_subparsers.add_parser("list", help="List feeds")
33+
add_as_list_arg(list_parser)
34+
add_pagination_args(list_parser)
3235
list_parser.set_defaults(func=handle_feed_list_command)
3336

3437
# Get feed command
@@ -87,7 +90,11 @@ def setup_feed_command(subparsers):
8790
def handle_feed_list_command(args, chronicle):
8891
"""Handle feed list command."""
8992
try:
90-
result = chronicle.list_feeds()
93+
result = chronicle.list_feeds(
94+
page_size=args.page_size,
95+
page_token=args.page_token,
96+
as_list=args.as_list,
97+
)
9198
output_formatter(result, args.output)
9299
except Exception as e: # pylint: disable=broad-exception-caught
93100
print(f"Error: {e}", file=sys.stderr)

src/secops/cli/commands/rule.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
#
1515
"""Google SecOps CLI rule commands"""
1616

17+
from ast import arg
1718
import json
1819
import sys
1920

2021
from secops.cli.utils.common_args import (
22+
add_as_list_arg,
2123
add_pagination_args,
2224
add_time_range_args,
2325
)
@@ -37,6 +39,7 @@ def setup_rule_command(subparsers):
3739
# List rules command
3840
list_parser = rule_subparsers.add_parser("list", help="List rules")
3941
add_pagination_args(list_parser)
42+
add_as_list_arg(list_parser)
4043
list_parser.add_argument(
4144
"--view",
4245
type=str,
@@ -152,6 +155,7 @@ def setup_rule_command(subparsers):
152155
"list-deployments", help="List rule deployments"
153156
)
154157
add_pagination_args(list_dep_parser)
158+
add_as_list_arg(list_dep_parser)
155159
list_dep_parser.add_argument(
156160
"--filter",
157161
dest="filter_query",
@@ -232,7 +236,10 @@ def handle_rule_list_command(args, chronicle):
232236
"""Handle rule list command."""
233237
try:
234238
result = chronicle.list_rules(
235-
view=args.view, page_size=args.page_size, page_token=args.page_token
239+
view=args.view,
240+
page_size=args.page_size,
241+
page_token=args.page_token,
242+
as_list=args.as_list,
236243
)
237244
output_formatter(result, args.output)
238245
except Exception as e: # pylint: disable=broad-exception-caught
@@ -397,6 +404,7 @@ def handle_rule_list_deployments_command(args, chronicle):
397404
filter_query=(
398405
args.filter_query if hasattr(args, "filter_query") else None
399406
),
407+
as_list=args.as_list,
400408
)
401409
output_formatter(result, args.output)
402410
except Exception as e: # pylint: disable=broad-exception-caught

tests/chronicle/test_case.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,3 +808,24 @@ def test_patch_case_json_parse_error(chronicle_client):
808808
):
809809
with pytest.raises(APIError):
810810
patch_case(chronicle_client, "12345", {"status": "CLOSED"})
811+
812+
813+
def test_list_cases_as_list(chronicle_client):
814+
"""Test list_cases function returning a list of cases."""
815+
mock_response = Mock()
816+
mock_response.status_code = 200
817+
mock_response.json.return_value = {
818+
"cases": [
819+
{"id": "case-1", "displayName": "First Case"},
820+
{"id": "case-2", "displayName": "Second Case"},
821+
]
822+
}
823+
with patch.object(
824+
chronicle_client.session, "request", return_value=mock_response
825+
):
826+
result = list_cases(chronicle_client, as_list=True)
827+
828+
assert isinstance(result, list)
829+
assert len(result) == 2
830+
assert result[0]["id"] == "case-1"
831+
assert result[1]["id"] == "case-2"

tests/chronicle/test_client.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
# limitations under the License.
1414
#
1515
"""Tests for Chronicle API client."""
16-
from datetime import datetime, timezone, timedelta
17-
import pytest
16+
from datetime import datetime, timedelta, timezone
1817
from unittest.mock import Mock, patch
18+
19+
import pytest
20+
1921
from secops.chronicle.client import ChronicleClient
20-
from secops.chronicle.models import CaseList
21-
from secops.exceptions import APIError, SecOpsError
22+
from secops.chronicle.models import APIVersion, CaseList
23+
from secops.exceptions import APIError
2224

2325

2426
@pytest.fixture
@@ -723,3 +725,20 @@ def test_fix_json_formatting(chronicle_client):
723725
json_without_trailing_commas = '{"a": [1, 2], "b": {"c": 3, "d": 4}}'
724726
fixed = chronicle_client._fix_json_formatting(json_without_trailing_commas)
725727
assert fixed == json_without_trailing_commas
728+
729+
@patch("secops.chronicle.client._list_rules")
730+
def test_client_list_rules_as_list(mock_list_rules, chronicle_client):
731+
"""Test that ChronicleClient.list_rules passes the as_list parameter."""
732+
mock_list_rules.return_value = [{"name": "rule1"}]
733+
734+
result = chronicle_client.list_rules(as_list=True)
735+
736+
mock_list_rules.assert_called_once_with(
737+
chronicle_client,
738+
view="FULL",
739+
page_size=None,
740+
page_token=None,
741+
api_version=APIVersion.V1,
742+
as_list=True
743+
)
744+
assert isinstance(result, list)

tests/chronicle/test_rule.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,3 +772,39 @@ def test_list_rule_deployments_with_filter(chronicle_client, mock_response):
772772
]
773773
}
774774
assert len(result["ruleDeployments"]) == 1
775+
776+
777+
def test_list_rules_as_list(chronicle_client):
778+
"""Test list_rules function with as_list=True."""
779+
mock_response = Mock()
780+
mock_response.status_code = 200
781+
mock_response.json.return_value = {
782+
"rules": [{"name": "rule1"}, {"name": "rule2"}]
783+
}
784+
785+
with patch.object(
786+
chronicle_client.session, "request", return_value=mock_response
787+
) as mock_get:
788+
# Act
789+
result = list_rules(chronicle_client, as_list=True)
790+
791+
# Assert
792+
mock_get.assert_called_once()
793+
assert isinstance(result, list)
794+
assert len(result) == 2
795+
assert result[0]["name"] == "rule1"
796+
assert result[1]["name"] == "rule2"
797+
798+
799+
def test_list_rules_empty_as_list(chronicle_client):
800+
"""Test list_rules function with as_list=True when no rules exist."""
801+
mock_response = Mock()
802+
mock_response.status_code = 200
803+
mock_response.json.return_value = {}
804+
805+
with patch.object(
806+
chronicle_client.session, "request", return_value=mock_response
807+
):
808+
result = list_rules(chronicle_client, as_list=True)
809+
assert isinstance(result, list)
810+
assert len(result) == 0

tests/chronicle/test_rule_detection.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,20 @@ def test_list_errors_api_error(chronicle_client, response_mock):
194194

195195
with pytest.raises(APIError, match="Failed to list rule errors"):
196196
rule_detection.list_errors(chronicle_client, rule_id="ru_missing")
197+
198+
199+
def test_list_detections_as_list(chronicle_client, response_mock):
200+
"""Test list_detections returning a list when as_list=True."""
201+
chronicle_client.session.request.return_value = response_mock
202+
203+
result = rule_detection.list_detections(
204+
chronicle_client,
205+
rule_id="ru_12345678-1234-1234-1234-1234567890ab",
206+
page_size=1,
207+
as_list=True,
208+
)
209+
210+
chronicle_client.session.request.assert_called_once()
211+
assert isinstance(result, list)
212+
assert len(result) == 1
213+
assert result[0]["id"] == "de_12345678-1234-1234-1234-1234567890ab"

tests/chronicle/test_watchlist.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,24 @@ def test_update_watchlist_error(chronicle_client):
455455
)
456456

457457
assert "Failed to update watchlist" in str(exc_info.value)
458+
459+
460+
def test_list_watchlists_as_list(chronicle_client):
461+
"""Test list_watchlists function returning a list."""
462+
mock_response = Mock()
463+
mock_response.status_code = 200
464+
mock_response.json.return_value = {
465+
"watchlists": [
466+
{"name": "watchlist-1", "displayName": "VIPs"},
467+
{"name": "watchlist-2", "displayName": "Suspicious IPs"},
468+
]
469+
}
470+
with patch.object(
471+
chronicle_client.session, "request", return_value=mock_response
472+
):
473+
result = list_watchlists(chronicle_client, as_list=True)
474+
475+
assert isinstance(result, list)
476+
assert len(result) == 2
477+
assert result[0]["name"] == "watchlist-1"
478+
assert result[1]["displayName"] == "Suspicious IPs"

0 commit comments

Comments
 (0)