Skip to content

Commit e65f6ac

Browse files
committed
add input validation for 'queries' in 'update_question()' method
1 parent 2b12fe8 commit e65f6ac

File tree

2 files changed

+121
-3
lines changed

2 files changed

+121
-3
lines changed

jupiterone/client.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,31 @@ def update_question(
15181518
if description is not None:
15191519
update_data["description"] = description
15201520
if queries is not None:
1521-
update_data["queries"] = queries
1521+
# Validate queries input using the same logic as create_question
1522+
if not isinstance(queries, list) or len(queries) == 0:
1523+
raise ValueError("queries must be a non-empty list")
1524+
1525+
# Process each query to ensure required fields
1526+
processed_queries = []
1527+
for idx, query in enumerate(queries):
1528+
if not isinstance(query, dict):
1529+
raise ValueError(f"Query at index {idx} must be a dictionary")
1530+
if "query" not in query:
1531+
raise ValueError(f"Query at index {idx} must have a 'query' field")
1532+
1533+
processed_query = {
1534+
"query": query["query"],
1535+
"name": query.get("name", f"Query{idx}"),
1536+
"resultsAre": query.get("resultsAre", "INFORMATIVE")
1537+
}
1538+
1539+
# Only add version if provided
1540+
if "version" in query:
1541+
processed_query["version"] = query["version"]
1542+
1543+
processed_queries.append(processed_query)
1544+
1545+
update_data["queries"] = processed_queries
15221546
if tags is not None:
15231547
update_data["tags"] = tags
15241548

tests/test_question_management.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,13 @@ def test_update_question_queries_only(self, mock_execute_query):
159159
# Check variables - they are in the second positional argument
160160
variables = call_args[0][1]
161161
assert variables['id'] == self.question_id
162-
assert variables['update']['queries'] == new_queries
162+
# The queries are now processed and enriched with default values
163+
processed_queries = variables['update']['queries']
164+
assert len(processed_queries) == 1
165+
assert processed_queries[0]['name'] == "UpdatedQuery"
166+
assert processed_queries[0]['query'] == "FIND User WITH active=true"
167+
assert processed_queries[0]['version'] == "v2"
168+
assert processed_queries[0]['resultsAre'] == "INFORMATIVE" # Default value added
163169
assert 'title' not in variables['update']
164170
assert 'description' not in variables['update']
165171
assert 'tags' not in variables['update']
@@ -204,7 +210,13 @@ def test_update_question_comprehensive(self, mock_execute_query):
204210
assert variables['update']['title'] == update_data['title']
205211
assert variables['update']['description'] == update_data['description']
206212
assert variables['update']['tags'] == update_data['tags']
207-
assert variables['update']['queries'] == update_data['queries']
213+
# The queries are now processed and enriched with default values
214+
processed_queries = variables['update']['queries']
215+
assert len(processed_queries) == 1
216+
assert processed_queries[0]['name'] == "ComprehensiveQuery"
217+
assert processed_queries[0]['query'] == "FIND * WITH _class='Host'"
218+
assert processed_queries[0]['version'] == "v3"
219+
assert processed_queries[0]['resultsAre'] == "INFORMATIVE" # Default value added
208220

209221
# Check result
210222
assert result['title'] == update_data['title']
@@ -347,3 +359,85 @@ def test_delete_question_nonexistent_question(self, mock_execute_query):
347359
result = self.client.delete_question(question_id="nonexistent-id")
348360

349361
assert result is None
362+
363+
# Tests for queries validation in update_question
364+
def test_update_question_queries_validation_empty_list(self):
365+
"""Test that update_question rejects empty queries list"""
366+
with pytest.raises(ValueError, match="queries must be a non-empty list"):
367+
self.client.update_question(
368+
question_id=self.question_id,
369+
queries=[]
370+
)
371+
372+
def test_update_question_queries_validation_not_list(self):
373+
"""Test that update_question rejects non-list queries"""
374+
with pytest.raises(ValueError, match="queries must be a non-empty list"):
375+
self.client.update_question(
376+
question_id=self.question_id,
377+
queries="not a list"
378+
)
379+
380+
def test_update_question_queries_validation_none(self):
381+
"""Test that update_question accepts None queries (no update)"""
382+
# This should not raise an error since queries=None means no update
383+
# The validation only happens when queries is provided
384+
try:
385+
self.client.update_question(
386+
question_id=self.question_id,
387+
title="Updated Title"
388+
)
389+
except Exception as e:
390+
# If it gets to the API call, that's fine - we're just testing validation
391+
pass
392+
393+
def test_update_question_queries_validation_missing_query_field(self):
394+
"""Test that update_question rejects queries missing 'query' field"""
395+
with pytest.raises(ValueError, match="Query at index 0 must have a 'query' field"):
396+
self.client.update_question(
397+
question_id=self.question_id,
398+
queries=[{"name": "InvalidQuery"}]
399+
)
400+
401+
def test_update_question_queries_validation_invalid_query_type(self):
402+
"""Test that update_question rejects non-dict query items"""
403+
with pytest.raises(ValueError, match="Query at index 0 must be a dictionary"):
404+
self.client.update_question(
405+
question_id=self.question_id,
406+
queries=["not a dict"]
407+
)
408+
409+
@patch('jupiterone.client.JupiterOneClient._execute_query')
410+
def test_update_question_queries_validation_success(self, mock_execute_query):
411+
"""Test that update_question successfully processes valid queries"""
412+
mock_response = {
413+
"data": {
414+
"updateQuestion": {
415+
"id": self.question_id,
416+
"title": "Updated Title",
417+
"queries": [
418+
{
419+
"name": "Query0",
420+
"query": "FIND Host",
421+
"resultsAre": "INFORMATIVE"
422+
}
423+
]
424+
}
425+
}
426+
}
427+
mock_execute_query.return_value = mock_response
428+
429+
result = self.client.update_question(
430+
question_id=self.question_id,
431+
queries=[{"query": "FIND Host"}]
432+
)
433+
434+
# Verify the call was made with processed queries
435+
call_args = mock_execute_query.call_args
436+
variables = call_args[0][1]
437+
assert variables['update']['queries'][0]['name'] == "Query0"
438+
assert variables['update']['queries'][0]['query'] == "FIND Host"
439+
assert variables['update']['queries'][0]['resultsAre'] == "INFORMATIVE"
440+
441+
# Check result
442+
assert result['id'] == self.question_id
443+
assert result['title'] == "Updated Title"

0 commit comments

Comments
 (0)