Skip to content

Commit ef05740

Browse files
committed
chore: updated unit test test_worker to get coverage to 97%.
Signed-off-by: habeck <habeck@us.ibm.com>
1 parent fa91525 commit ef05740

1 file changed

Lines changed: 169 additions & 1 deletion

File tree

tests/unit/cpex/framework/isolated/test_worker.py

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
import asyncio
1111
import json
1212
import sys
13+
from io import StringIO
1314
from pathlib import Path
1415
from unittest.mock import AsyncMock, MagicMock, Mock, patch
1516

1617
import pytest
1718

18-
from cpex.framework.isolated.worker import get_environment_info, get_proper_config, process_task
19+
from cpex.framework.isolated.worker import get_environment_info, get_proper_config, main, process_task
1920

2021

2122
class TestWorkerFunctions:
@@ -290,5 +291,172 @@ async def test_process_task_with_metadata(self, mock_executor_class, mock_import
290291
assert call_args is not None
291292

292293

294+
class TestMainFunction:
295+
"""Test suite for the main() function."""
296+
297+
@pytest.mark.asyncio
298+
@patch("sys.stdin")
299+
@patch("builtins.print")
300+
@patch("cpex.framework.isolated.worker.process_task")
301+
async def test_main_success_with_info_task(self, mock_process_task, mock_print, mock_stdin):
302+
"""Test main function with successful info task."""
303+
# Setup stdin with info task
304+
task_data = {"task_type": "info"}
305+
mock_stdin.read.return_value = json.dumps(task_data)
306+
307+
# Setup process_task to return a mock result
308+
mock_result = MagicMock()
309+
mock_result.model_dump.return_value = {
310+
"status": "success",
311+
"environment": {"python_version": "3.10"},
312+
"message": "Environment info retrieved successfully",
313+
}
314+
mock_process_task.return_value = mock_result
315+
316+
# Run main
317+
await main()
318+
319+
# Verify process_task was called with correct data
320+
mock_process_task.assert_called_once_with(task_data)
321+
322+
# Verify output was printed
323+
mock_print.assert_called_once()
324+
printed_output = mock_print.call_args[0][0]
325+
output_data = json.loads(printed_output)
326+
assert output_data["status"] == "success"
327+
328+
@pytest.mark.asyncio
329+
@patch("sys.stdin")
330+
@patch("builtins.print")
331+
@patch("cpex.framework.isolated.worker.process_task")
332+
async def test_main_success_with_none_result(self, mock_process_task, mock_print, mock_stdin):
333+
"""Test main function when process_task returns None."""
334+
task_data = {"task_type": "unknown"}
335+
mock_stdin.read.return_value = json.dumps(task_data)
336+
337+
# process_task returns None for unknown task types
338+
mock_process_task.return_value = None
339+
340+
await main()
341+
342+
mock_process_task.assert_called_once_with(task_data)
343+
mock_print.assert_called_once()
344+
printed_output = mock_print.call_args[0][0]
345+
# Should print "null" for None
346+
assert printed_output == "null"
347+
348+
@pytest.mark.asyncio
349+
@patch("sys.stdin")
350+
@patch("builtins.print")
351+
async def test_main_json_decode_error(self, mock_print, mock_stdin):
352+
"""Test main function with invalid JSON input."""
353+
# Setup stdin with invalid JSON
354+
mock_stdin.read.return_value = "not valid json {{"
355+
356+
await main()
357+
358+
# Verify error response was printed
359+
mock_print.assert_called_once()
360+
printed_output = mock_print.call_args[0][0]
361+
output_data = json.loads(printed_output)
362+
assert output_data["status"] == "error"
363+
assert output_data["message"] == "Invalid JSON input"
364+
365+
@pytest.mark.asyncio
366+
@patch("sys.stdin")
367+
@patch("builtins.print")
368+
@patch("cpex.framework.isolated.worker.process_task")
369+
async def test_main_unexpected_exception(self, mock_process_task, mock_print, mock_stdin):
370+
"""Test main function with unexpected exception during processing."""
371+
task_data = {"task_type": "load_and_run_hook"}
372+
mock_stdin.read.return_value = json.dumps(task_data)
373+
374+
# Make process_task raise an exception
375+
mock_process_task.side_effect = RuntimeError("Unexpected error occurred")
376+
377+
await main()
378+
379+
# Verify error response was printed
380+
mock_print.assert_called_once()
381+
printed_output = mock_print.call_args[0][0]
382+
output_data = json.loads(printed_output)
383+
assert output_data["status"] == "error"
384+
assert "Unexpected error: Unexpected error occurred" in output_data["message"]
385+
386+
@pytest.mark.asyncio
387+
@patch("sys.stdin")
388+
@patch("builtins.print")
389+
@patch("cpex.framework.isolated.worker.process_task")
390+
async def test_main_with_load_and_run_hook_task(self, mock_process_task, mock_print, mock_stdin):
391+
"""Test main function with load_and_run_hook task."""
392+
config_dict = {"name": "test_plugin", "kind": "isolated_venv"}
393+
task_data = {
394+
"task_type": "load_and_run_hook",
395+
"config": json.dumps(config_dict),
396+
"script_path": "plugins",
397+
"class_name": "test_plugin.TestPlugin",
398+
"hook_type": "tool_pre_invoke",
399+
"payload": {"name": "test_tool"},
400+
"context": {"state": {}, "global_context": {}, "metadata": {}},
401+
}
402+
mock_stdin.read.return_value = json.dumps(task_data)
403+
404+
# Setup mock result
405+
mock_result = MagicMock()
406+
mock_result.model_dump.return_value = {
407+
"continue_processing": True,
408+
"payload": {"name": "test_tool", "modified": True},
409+
"violations": [],
410+
}
411+
mock_process_task.return_value = mock_result
412+
413+
await main()
414+
415+
mock_process_task.assert_called_once_with(task_data)
416+
mock_print.assert_called_once()
417+
printed_output = mock_print.call_args[0][0]
418+
output_data = json.loads(printed_output)
419+
assert output_data["continue_processing"] is True
420+
421+
@pytest.mark.asyncio
422+
@patch("sys.stdin")
423+
@patch("builtins.print")
424+
@patch("cpex.framework.isolated.worker.process_task")
425+
async def test_main_with_empty_stdin(self, mock_process_task, mock_print, mock_stdin):
426+
"""Test main function with empty stdin."""
427+
mock_stdin.read.return_value = ""
428+
429+
await main()
430+
431+
# Should handle as JSON decode error
432+
mock_print.assert_called_once()
433+
printed_output = mock_print.call_args[0][0]
434+
output_data = json.loads(printed_output)
435+
assert output_data["status"] == "error"
436+
assert output_data["message"] == "Invalid JSON input"
437+
438+
@pytest.mark.asyncio
439+
@patch("sys.stdin")
440+
@patch("builtins.print")
441+
@patch("cpex.framework.isolated.worker.process_task")
442+
async def test_main_with_model_dump_exception(self, mock_process_task, mock_print, mock_stdin):
443+
"""Test main function when model_dump raises an exception."""
444+
task_data = {"task_type": "info"}
445+
mock_stdin.read.return_value = json.dumps(task_data)
446+
447+
# Setup mock result that raises exception on model_dump
448+
mock_result = MagicMock()
449+
mock_result.model_dump.side_effect = ValueError("Cannot serialize")
450+
mock_process_task.return_value = mock_result
451+
452+
await main()
453+
454+
# Should catch the exception and return error
455+
mock_print.assert_called_once()
456+
printed_output = mock_print.call_args[0][0]
457+
output_data = json.loads(printed_output)
458+
assert output_data["status"] == "error"
459+
assert "Unexpected error" in output_data["message"]
460+
293461

294462
# Made with Bob

0 commit comments

Comments
 (0)