"""Tests for Lambda function utilities.""" from collections import namedtuple from io import StringIO import json from unittest.mock import MagicMock, patch from botocore.exceptions import ClientError import pytest from collaborator.utils import lambda_fn from collaborator.utils.error import OwsError @patch("collaborator.utils.lambda_fn.boto3") def test_invoke_lambda_success(mock_boto3): """Test successfully invoking a Lambda function.""" mock_client = MagicMock() mock_boto3.client.return_value = mock_client # Mock successful response with payload expected_response = {"report_run_id": 123} mock_payload = StringIO(json.dumps(expected_response)) mock_client.invoke.return_value = {"Payload": mock_payload} function_name = "test-lambda-function" payload = {"key1": "value1", "key2": 123} result = lambda_fn.invoke_lambda(function_name, payload) mock_boto3.client.assert_called_once_with("lambda") mock_client.invoke.assert_called_once_with( FunctionName=function_name, InvocationType="RequestResponse", Payload=json.dumps(payload), ) assert result == expected_response @patch("collaborator.utils.lambda_fn.boto3") def test_invoke_lambda_success_no_payload(mock_boto3): """Test successfully invoking a Lambda function with no response payload.""" mock_client = MagicMock() mock_boto3.client.return_value = mock_client mock_client.invoke.return_value = {} # No Payload function_name = "test-lambda-function" payload = {"key1": "value1"} result = lambda_fn.invoke_lambda(function_name, payload) assert result == {} ClientErrorTest = namedtuple( "ClientErrorTest", [ "error_code", "error_message", "status_code", "expected_status", ], ) @pytest.mark.parametrize( ClientErrorTest._fields, [ ClientErrorTest( error_code="ResourceNotFoundException", error_message="Function not found", status_code=404, expected_status=404, ), ClientErrorTest( error_code="ServiceException", error_message="Internal service error", status_code=None, # No status code in ResponseMetadata expected_status=500, # Should default to INTERNAL_ERROR ), ClientErrorTest( error_code="AccessDeniedException", error_message="User is not authorized to perform lambda:InvokeFunction", status_code=403, expected_status=403, ), ], ) @patch("collaborator.utils.lambda_fn.boto3") def test_invoke_lambda_client_error( mock_boto3, error_code, error_message, status_code, expected_status ): """Test Lambda invocation with various ClientError scenarios.""" mock_client = MagicMock() mock_boto3.client.return_value = mock_client error_response = { "Error": {"Code": error_code, "Message": error_message}, "ResponseMetadata": ({"HTTPStatusCode": status_code} if status_code else {}), } mock_client.invoke.side_effect = ClientError(error_response, "Invoke") with pytest.raises(OwsError) as exc_info: lambda_fn.invoke_lambda("test-function", {"test": "data"}) assert f"{error_code}: {error_message}" in str(exc_info.value.message) assert exc_info.value.status == expected_status FunctionErrorTest = namedtuple( "FunctionErrorTest", [ "payload_data", "has_payload", "expected_message", ], ) @pytest.mark.parametrize( FunctionErrorTest._fields, [ # With error message in payload FunctionErrorTest( payload_data={"errorMessage": "Division by zero"}, has_payload=True, expected_message="Lambda function error: Division by zero", ), # Without error message in payload FunctionErrorTest( payload_data={"someOtherField": "value"}, has_payload=True, expected_message="Lambda function error: Unknown error", ), # Without payload FunctionErrorTest( payload_data=None, has_payload=False, expected_message="Lambda function error: Unknown error", ), ], ) @patch("collaborator.utils.lambda_fn.boto3") def test_invoke_lambda_function_error( mock_boto3, payload_data, has_payload, expected_message ): """Test Lambda invocation when function returns various error scenarios.""" mock_client = MagicMock() mock_boto3.client.return_value = mock_client response = {"FunctionError": "Unhandled"} if has_payload: response["Payload"] = StringIO(json.dumps(payload_data)) mock_client.invoke.return_value = response with pytest.raises(OwsError) as exc_info: lambda_fn.invoke_lambda("test-function", {"test": "data"}) assert expected_message in str(exc_info.value.message) assert exc_info.value.status == 500 else: mock_client.invoke.return_value = response with pytest.raises(OwsError) as exc_info: lambda_fn.invoke_lambda("test-function", {"test": "data"}) assert expected_message in str(exc_info.value.message) assert exc_info.value.status == 500