Skip to content

Commit dd45f54

Browse files
committed
chore: added integration tests. fixed integration instance flags.
1 parent aecfbff commit dd45f54

8 files changed

Lines changed: 1187 additions & 18 deletions

src/secops/cli/commands/integration/integration_instances.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ def setup_integration_instances_command(subparsers):
114114
type=str,
115115
help="Display name for the instance",
116116
dest="display_name",
117-
required=True,
118117
)
119118
create_parser.add_argument(
120119
"--environment",
@@ -129,18 +128,18 @@ def setup_integration_instances_command(subparsers):
129128
help="Description of the instance",
130129
dest="description",
131130
)
132-
create_parser.add_argument(
133-
"--instance-id",
134-
type=str,
135-
help="Custom ID for the instance",
136-
dest="instance_id",
137-
)
138131
create_parser.add_argument(
139132
"--config",
140133
type=str,
141134
help="JSON string of instance configuration",
142135
dest="config",
143136
)
137+
create_parser.add_argument(
138+
"--agent",
139+
type=str,
140+
help="Agent identifier for a remote integration instance",
141+
dest="agent",
142+
)
144143
create_parser.set_defaults(func=handle_integration_instances_create_command)
145144

146145
# update command
@@ -167,6 +166,12 @@ def setup_integration_instances_command(subparsers):
167166
help="New display name for the instance",
168167
dest="display_name",
169168
)
169+
update_parser.add_argument(
170+
"--environment",
171+
type=str,
172+
help="New environment name for the instance",
173+
dest="environment",
174+
)
170175
update_parser.add_argument(
171176
"--description",
172177
type=str,
@@ -179,6 +184,12 @@ def setup_integration_instances_command(subparsers):
179184
help="JSON string of new instance configuration",
180185
dest="config",
181186
)
187+
update_parser.add_argument(
188+
"--agent",
189+
type=str,
190+
help="New agent identifier for a remote integration instance",
191+
dest="agent",
192+
)
182193
update_parser.add_argument(
183194
"--update-mask",
184195
type=str,
@@ -304,8 +315,8 @@ def handle_integration_instances_create_command(args, chronicle):
304315
display_name=args.display_name,
305316
environment=args.environment,
306317
description=args.description,
307-
integration_instance_id=args.instance_id,
308-
config=config,
318+
parameters=config,
319+
agent=getattr(args, "agent", None),
309320
)
310321
output_formatter(out, getattr(args, "output", "json"))
311322
except json.JSONDecodeError as e:
@@ -329,8 +340,10 @@ def handle_integration_instances_update_command(args, chronicle):
329340
integration_name=args.integration_name,
330341
integration_instance_id=args.instance_id,
331342
display_name=args.display_name,
343+
environment=getattr(args, "environment", None),
332344
description=args.description,
333-
config=config,
345+
parameters=config,
346+
agent=getattr(args, "agent", None),
334347
update_mask=args.update_mask,
335348
)
336349
output_formatter(out, getattr(args, "output", "json"))
@@ -345,16 +358,9 @@ def handle_integration_instances_update_command(args, chronicle):
345358
def handle_integration_instances_test_command(args, chronicle):
346359
"""Handle integration instance test command"""
347360
try:
348-
# Get the instance first
349-
instance = chronicle.soar.get_integration_instance(
350-
integration_name=args.integration_name,
351-
integration_instance_id=args.instance_id,
352-
)
353-
354361
out = chronicle.soar.execute_integration_instance_test(
355362
integration_name=args.integration_name,
356363
integration_instance_id=args.instance_id,
357-
integration_instance=instance,
358364
)
359365
output_formatter(out, getattr(args, "output", "json"))
360366
except Exception as e: # pylint: disable=broad-exception-caught
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
"""Integration tests for Chronicle integration instances.
16+
17+
These tests require valid credentials and API access.
18+
"""
19+
20+
import time
21+
import uuid
22+
23+
import pytest
24+
25+
from secops import SecOpsClient
26+
from secops.exceptions import APIError
27+
from tests.config import CHRONICLE_CONFIG, SERVICE_ACCOUNT_JSON
28+
29+
30+
@pytest.mark.integration
31+
def test_integration_instances_crud_workflow():
32+
"""Test full integration instance lifecycle: create, get, update, delete.
33+
34+
TODO: Remove 401 skip logic once SOAR IAM role issue is fixed.
35+
"""
36+
client = SecOpsClient()
37+
chronicle = client.chronicle(**CHRONICLE_CONFIG)
38+
39+
integration_name = None
40+
created_instance_id = None
41+
42+
try:
43+
print("\n1. Finding a target integration")
44+
integrations_resp = chronicle.soar.list_integrations(page_size=1)
45+
integrations = integrations_resp.get("integrations", [])
46+
if not integrations:
47+
pytest.skip("No integrations available to test instance creation.")
48+
49+
integration_name = integrations[0]["name"].split("/")[-1]
50+
print(f"Using integration: {integration_name}")
51+
52+
print("\n2. Creating integration instance")
53+
unique_id = str(uuid.uuid4())[:8]
54+
display_name = f"SDK Test Instance {unique_id}"
55+
environment = "Default Environment"
56+
57+
create_resp = chronicle.soar.create_integration_instance(
58+
integration_name=integration_name,
59+
environment=environment,
60+
display_name=display_name,
61+
description="Created by integration test",
62+
)
63+
64+
assert create_resp is not None
65+
assert "name" in create_resp
66+
created_instance_id = create_resp["name"].split("/")[-1]
67+
assert create_resp.get("displayName") == display_name
68+
print(f"Created instance with ID: {created_instance_id}")
69+
70+
time.sleep(2)
71+
72+
print("\n3. Getting instance details")
73+
get_resp = chronicle.soar.get_integration_instance(
74+
integration_name=integration_name,
75+
integration_instance_id=created_instance_id,
76+
)
77+
78+
assert get_resp.get("displayName") == display_name
79+
print(f"Successfully retrieved instance: {display_name}")
80+
81+
print("\n4. Updating instance")
82+
updated_name = f"Updated SDK Test Instance {unique_id}"
83+
update_resp = chronicle.soar.update_integration_instance(
84+
integration_name=integration_name,
85+
integration_instance_id=created_instance_id,
86+
display_name=updated_name,
87+
description="Updated description",
88+
)
89+
90+
assert update_resp.get("displayName") == updated_name
91+
print(f"Successfully updated instance: {updated_name}")
92+
93+
print("\n5. Executing integration instance test")
94+
try:
95+
test_resp = chronicle.soar.execute_integration_instance_test(
96+
integration_name=integration_name,
97+
integration_instance_id=created_instance_id,
98+
)
99+
assert "successful" in test_resp
100+
print(f"Test executed. Successful: {test_resp.get('successful')}")
101+
except APIError as e:
102+
print(
103+
f"Test execution returned API error (expected for some configs): {e}"
104+
)
105+
106+
print("\n6. Listing integration instances")
107+
list_resp = chronicle.soar.list_integration_instances(
108+
integration_name=integration_name,
109+
page_size=10,
110+
)
111+
112+
instances = list_resp.get("integrationInstances", [])
113+
found = any(
114+
i.get("name", "").endswith(created_instance_id) for i in instances
115+
)
116+
assert (
117+
found
118+
), f"Created instance {created_instance_id} not found in list."
119+
print("Successfully found instance in list results")
120+
121+
print("\n7. Getting affected items")
122+
affected_resp = chronicle.soar.get_integration_instance_affected_items(
123+
integration_name=integration_name,
124+
integration_instance_id=created_instance_id,
125+
)
126+
# Assuming response is a dict with affectedPlaybooks or affectedItems
127+
assert isinstance(affected_resp, dict)
128+
print("Successfully retrieved affected items")
129+
130+
except APIError as e:
131+
error_msg = str(e)
132+
if "401" in error_msg or "Unauthorized" in error_msg:
133+
pytest.skip(f"Skipping due to SOAR IAM issue (401): {e}")
134+
if "400" in error_msg:
135+
pytest.skip(
136+
f"Skipping due to 400 Bad Request (integration may require specific parameters): {e}"
137+
)
138+
raise
139+
finally:
140+
print("\n8. Cleaning up: Deleting integration instance")
141+
if integration_name and created_instance_id:
142+
try:
143+
chronicle.soar.delete_integration_instance(
144+
integration_name=integration_name,
145+
integration_instance_id=created_instance_id,
146+
)
147+
print(f"Successfully deleted instance: {created_instance_id}")
148+
except Exception as cleanup_error:
149+
print(
150+
f"Warning: Failed to delete test instance: {cleanup_error}"
151+
)
152+
153+
154+
@pytest.mark.integration
155+
def test_get_default_integration_instance():
156+
"""Test getting default integration instance.
157+
158+
TODO: Remove 401 skip logic once SOAR IAM role issue is fixed.
159+
"""
160+
client = SecOpsClient()
161+
chronicle = client.chronicle(**CHRONICLE_CONFIG)
162+
163+
try:
164+
print("\n1. Finding a target integration")
165+
integrations_resp = chronicle.soar.list_integrations(page_size=1)
166+
integrations = integrations_resp.get("integrations", [])
167+
if not integrations:
168+
pytest.skip("No integrations available.")
169+
170+
integration_name = integrations[0]["name"].split("/")[-1]
171+
print(f"Using integration: {integration_name}")
172+
173+
print("\n2. Getting default instance")
174+
default_instance = chronicle.soar.get_default_integration_instance(
175+
integration_name=integration_name
176+
)
177+
178+
assert default_instance is not None
179+
assert "name" in default_instance.get("instance")
180+
print(
181+
"Successfully retrieved default instance: "
182+
f"{default_instance.get('instance').get('name')}"
183+
)
184+
185+
except APIError as e:
186+
error_msg = str(e)
187+
if "401" in error_msg or "Unauthorized" in error_msg:
188+
pytest.skip(f"Skipping due to SOAR IAM issue (401): {e}")
189+
if "400" in error_msg or "404" in error_msg:
190+
pytest.skip(
191+
f"Skipping due to {error_msg} (integration may not have a default instance)"
192+
)
193+
raise

0 commit comments

Comments
 (0)