Skip to content

Commit fd2d463

Browse files
committed
Send the ServiceNow incident payload through execute_command_proxy
In keyword form, FalconPy's execute_command_proxy ignores a request= argument, so the incident fields and headers were never sent and ServiceNow created a blank incident while the function reported success. Pass the full request as body= instead. The existing tests asserted the call arguments on a mock, which accepts any keywords, so a new test runs the real FalconPy method against a stubbed transport and checks the body it sends.
1 parent 3d3fc14 commit fd2d463

2 files changed

Lines changed: 66 additions & 33 deletions

File tree

‎functions/servicenow/main.py‎

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,25 @@ def on_post(request: Request, _config: Optional[Dict[str, object]], logger: Logg
6666

6767
try:
6868
# Use the APIIntegrations client to call ServiceNow Table API to create incident
69+
# Pass the full request as body=: in keyword form, FalconPy ignores a
70+
# request= argument, so the payload and headers would never be sent
6971
api = APIIntegrations()
70-
response = api.execute_command_proxy(
71-
definition_id="ServiceNow",
72-
operation_id="POST__api_now_table_tablename",
73-
params={
74-
"path": {"tableName": "incident"}
75-
},
76-
request={
77-
"json": payload,
78-
"headers": {
79-
"Accept": "application/json",
80-
"Content-Type": "application/json"
72+
response = api.execute_command_proxy(body={
73+
"resources": [{
74+
"definition_id": "ServiceNow",
75+
"operation_id": "POST__api_now_table_tablename",
76+
"request": {
77+
"params": {
78+
"path": {"tableName": "incident"},
79+
"header": {
80+
"Accept": "application/json",
81+
"Content-Type": "application/json"
82+
}
83+
},
84+
"json": payload
8185
}
82-
}
83-
)
86+
}]
87+
})
8488

8589
# Log the raw response for troubleshooting
8690
logger.info(f"ServiceNow API response: {response}")

‎functions/servicenow/test_main.py‎

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,27 @@ def test_on_post_success_minimal_fields(self, mock_api_integrations_class):
7575
self.assertEqual(response.body["url"], "https://instance.service-now.com/api/now/table/incident/abc123def456")
7676

7777
# Verify API call was made correctly
78-
mock_api_instance.execute_command_proxy.assert_called_once_with(
79-
definition_id="ServiceNow",
80-
operation_id="POST__api_now_table_tablename",
81-
params={
82-
"path": {"tableName": "incident"}
83-
},
84-
request={
85-
"json": {
86-
"short_description": "Test incident",
87-
"description": "This is a test incident description",
88-
"impact": "2",
89-
"urgency": "2"
90-
},
91-
"headers": {
92-
"Accept": "application/json",
93-
"Content-Type": "application/json"
78+
mock_api_instance.execute_command_proxy.assert_called_once_with(body={
79+
"resources": [{
80+
"definition_id": "ServiceNow",
81+
"operation_id": "POST__api_now_table_tablename",
82+
"request": {
83+
"params": {
84+
"path": {"tableName": "incident"},
85+
"header": {
86+
"Accept": "application/json",
87+
"Content-Type": "application/json"
88+
}
89+
},
90+
"json": {
91+
"short_description": "Test incident",
92+
"description": "This is a test incident description",
93+
"impact": "2",
94+
"urgency": "2"
95+
}
9496
}
95-
}
96-
)
97+
}]
98+
})
9799

98100
# Verify logger was called
99101
self.assertEqual(mock_logger.info.call_count, 2)
@@ -145,7 +147,7 @@ def test_on_post_success_all_fields(self, mock_api_integrations_class):
145147

146148
# Verify API call included all optional fields
147149
call_args = mock_api_instance.execute_command_proxy.call_args
148-
payload = call_args[1]["request"]["json"]
150+
payload = call_args[1]["body"]["resources"][0]["request"]["json"]
149151
self.assertEqual(payload["impact"], "1")
150152
self.assertEqual(payload["urgency"], "1")
151153
self.assertEqual(payload["category"], "Security")
@@ -359,13 +361,40 @@ def test_on_post_optional_fields_filtering(self, mock_api_integrations_class):
359361

360362
# Verify API call payload excludes None values but includes empty strings
361363
call_args = mock_api_instance.execute_command_proxy.call_args
362-
payload = call_args[1]["request"]["json"]
364+
payload = call_args[1]["body"]["resources"][0]["request"]["json"]
363365
self.assertEqual(payload["impact"], "3")
364366
self.assertEqual(payload["category"], "Hardware")
365367
self.assertEqual(payload["assignment_group"], "")
366368
self.assertNotIn("subcategory", payload)
367369
self.assertNotIn("caller_id", payload)
368370

369371

372+
def test_payload_survives_falconpy(self):
373+
"""The request FalconPy actually sends carries the incident payload.
374+
375+
A mocked execute_command_proxy accepts any keywords, so this runs the
376+
real method against a stubbed transport and checks the outgoing body.
377+
"""
378+
from falconpy import APIIntegrations # pylint: disable=import-outside-toplevel
379+
380+
sent = {}
381+
382+
def fake_request(*_args, **kwargs):
383+
sent.update(kwargs)
384+
return {"status_code": 201, "headers": {"Location": "x"}, "body": {"result": {
385+
"sys_id": "1", "number": "INC1", "state": "1", "priority": "3",
386+
"sys_created_on": "now", "assigned_to": ""}}}
387+
388+
request = Request()
389+
request.body = {"title": "Test incident", "description": "Body must reach ServiceNow"}
390+
with patch("main.APIIntegrations", lambda: APIIntegrations(access_token="test")), \
391+
patch("falconpy._util._functions.service_request", side_effect=fake_request):
392+
response = main.on_post(request, None, MagicMock())
393+
394+
self.assertEqual(response.code, 201)
395+
resource = sent["body"]["resources"][0]
396+
self.assertEqual(resource["request"]["json"]["short_description"], "Test incident")
397+
self.assertEqual(resource["request"]["params"]["header"]["Content-Type"], "application/json")
398+
370399
if __name__ == "__main__":
371400
unittest.main()

0 commit comments

Comments
 (0)