From 69525c62dc55a789b5a200e5ce85571f7511ae56 Mon Sep 17 00:00:00 2001 From: SWhyteAnswer Date: Tue, 16 Dec 2025 16:18:18 +0000 Subject: [PATCH 1/4] [PRMP-1048] handler --- .../base-lambdas-reusable-deploy-all.yml | 14 +++++++ .../concurrency_controller_handler.py | 32 ++++++++++++++ .../concurrency_controller_service.py | 42 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 lambdas/handlers/concurrency_controller_handler.py create mode 100644 lambdas/services/concurrency_controller_service.py diff --git a/.github/workflows/base-lambdas-reusable-deploy-all.yml b/.github/workflows/base-lambdas-reusable-deploy-all.yml index d92a3893ab..b0a3f9e266 100644 --- a/.github/workflows/base-lambdas-reusable-deploy-all.yml +++ b/.github/workflows/base-lambdas-reusable-deploy-all.yml @@ -796,3 +796,17 @@ jobs: lambda_layer_names: "core_lambda_layer" secrets: AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} + + deploy_concurrency_controller_lambda: + name: Deploy Concurrency Controller Lambda + uses: ./.github/workflows/base-lambdas-reusable-deploy.yml + with: + environment: ${{ inputs.environment }} + python_version: ${{ inputs.python_version }} + build_branch: ${{ inputs.build_branch }} + sandbox: ${{ inputs.sandbox }} + lambda_handler_name: concurrency_controller_handler + lambda_aws_name: ConcurrencyController + lambda_layer_names: "core_lambda_layer" + secrets: + AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }} diff --git a/lambdas/handlers/concurrency_controller_handler.py b/lambdas/handlers/concurrency_controller_handler.py new file mode 100644 index 0000000000..c9cce11992 --- /dev/null +++ b/lambdas/handlers/concurrency_controller_handler.py @@ -0,0 +1,32 @@ +from services.concurrency_controller_service import ConcurrencyControllerService +from utils.audit_logging_setup import LoggingService +from utils.decorators.handle_lambda_exceptions import handle_lambda_exceptions +from utils.decorators.override_error_check import override_error_check +from utils.decorators.set_audit_arg import set_request_context_for_logging + +logger = LoggingService(__name__) + + +def validate_event(event): + target_function = event.get("targetFunction") + reserved_concurrency = event.get("reservedConcurrency") + + if not target_function: + logger.error("Missing required parameter: targetFunction") + raise ValueError("targetFunction is required") + + if reserved_concurrency is None: + logger.error("Missing required parameter: reservedConcurrency") + raise ValueError("reservedConcurrency is required") + + return target_function, reserved_concurrency + + +@set_request_context_for_logging +@override_error_check +@handle_lambda_exceptions +def lambda_handler(event, _context): + target_function, reserved_concurrency = validate_event(event) + + service = ConcurrencyControllerService() + return service.update_function_concurrency(target_function, reserved_concurrency) diff --git a/lambdas/services/concurrency_controller_service.py b/lambdas/services/concurrency_controller_service.py new file mode 100644 index 0000000000..3ea1e97b08 --- /dev/null +++ b/lambdas/services/concurrency_controller_service.py @@ -0,0 +1,42 @@ +import boto3 +from utils.audit_logging_setup import LoggingService + +logger = LoggingService(__name__) + + +class ConcurrencyControllerService: + def __init__(self): + self.lambda_client = boto3.client("lambda") + + def update_function_concurrency(self, target_function, reserved_concurrency): + logger.info( + f"Updating reserved concurrency for function '{target_function}' to {reserved_concurrency}" + ) + + try: + response = self.lambda_client.put_function_concurrency( + FunctionName=target_function, + ReservedConcurrentExecutions=reserved_concurrency + ) + + updated_concurrency = response.get("ReservedConcurrentExecutions") + + logger.info( + f"Successfully updated concurrency for '{target_function}'. " + f"Reserved concurrency set to: {updated_concurrency}" + ) + + return { + "statusCode": 200, + "body": { + "message": "Concurrency updated successfully", + "function": target_function, + "reservedConcurrency": updated_concurrency + } + } + except self.lambda_client.exceptions.ResourceNotFoundException: + logger.error(f"Lambda function '{target_function}' not found") + raise + except self.lambda_client.exceptions.InvalidParameterValueException as e: + logger.error(f"Invalid concurrency value: {e}") + raise From 2da9c725583885398291ce6ef09ba788df2dfcde Mon Sep 17 00:00:00 2001 From: SWhyteAnswer Date: Wed, 17 Dec 2025 14:55:41 +0000 Subject: [PATCH 2/4] [PRMP-1048] tests --- .../test_concurrency_controller_handler.py | 235 ++++++++++++++++++ .../test_concurrency_controller_service.py | 174 +++++++++++++ 2 files changed, 409 insertions(+) create mode 100644 lambdas/tests/unit/handlers/test_concurrency_controller_handler.py create mode 100644 lambdas/tests/unit/services/test_concurrency_controller_service.py diff --git a/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py new file mode 100644 index 0000000000..051e8d6d44 --- /dev/null +++ b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py @@ -0,0 +1,235 @@ +import json +import pytest +from botocore.exceptions import ClientError +from handlers.concurrency_controller_handler import lambda_handler, validate_event +from unittest.mock import MagicMock + + +@pytest.fixture +def mock_concurrency_controller_service(mocker): + mocked_class = mocker.patch( + "handlers.concurrency_controller_handler.ConcurrencyControllerService" + ) + mocked_instance = mocked_class.return_value + yield mocked_instance + + +@pytest.fixture +def mock_logger(mocker): + return mocker.patch("handlers.concurrency_controller_handler.logger") + + +@pytest.fixture +def valid_event(): + return { + "targetFunction": "test-lambda-function", + "reservedConcurrency": 10 + } + + +@pytest.fixture +def event_with_zero_concurrency(): + return { + "targetFunction": "test-lambda-function", + "reservedConcurrency": 0 + } + + +def test_lambda_handler_success(valid_event, context, mock_concurrency_controller_service): + expected_response = { + "statusCode": 200, + "body": { + "message": "Concurrency updated successfully", + "function": "test-lambda-function", + "reservedConcurrency": 10 + } + } + + mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + + result = lambda_handler(valid_event, context) + + mock_concurrency_controller_service.update_function_concurrency.assert_called_once_with( + "test-lambda-function", 10 + ) + + assert result == expected_response + + +def test_lambda_handler_with_zero_concurrency( + event_with_zero_concurrency, context, mock_concurrency_controller_service +): + expected_response = { + "statusCode": 200, + "body": { + "message": "Concurrency updated successfully", + "function": "test-lambda-function", + "reservedConcurrency": 0 + } + } + + mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + + result = lambda_handler(event_with_zero_concurrency, context) + + mock_concurrency_controller_service.update_function_concurrency.assert_called_once_with( + "test-lambda-function", 0 + ) + + assert result == expected_response + + +def test_lambda_handler_with_large_concurrency(context, mock_concurrency_controller_service): + event = { + "targetFunction": "test-lambda-function", + "reservedConcurrency": 1000 + } + + expected_response = { + "statusCode": 200, + "body": { + "message": "Concurrency updated successfully", + "function": "test-lambda-function", + "reservedConcurrency": 1000 + } + } + + mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + + result = lambda_handler(event, context) + + mock_concurrency_controller_service.update_function_concurrency.assert_called_once_with( + "test-lambda-function", 1000 + ) + + assert result == expected_response + + +def test_validate_event_success(valid_event): + target_function, reserved_concurrency = validate_event(valid_event) + + assert target_function == "test-lambda-function" + assert reserved_concurrency == 10 + + +def test_validate_event_missing_target_function(mock_logger): + event = { + "reservedConcurrency": 10 + } + + with pytest.raises(ValueError) as exc_info: + validate_event(event) + + assert str(exc_info.value) == "targetFunction is required" + mock_logger.error.assert_called_once_with("Missing required parameter: targetFunction") + + +def test_validate_event_missing_reserved_concurrency(mock_logger): + event = { + "targetFunction": "test-lambda-function" + } + + with pytest.raises(ValueError) as exc_info: + validate_event(event) + + assert str(exc_info.value) == "reservedConcurrency is required" + mock_logger.error.assert_called_once_with("Missing required parameter: reservedConcurrency") + + +def test_validate_event_both_parameters_missing(mock_logger): + event = {} + + with pytest.raises(ValueError) as exc_info: + validate_event(event) + + # Should fail on first missing parameter + assert str(exc_info.value) == "targetFunction is required" + + +def test_validate_event_empty_target_function(mock_logger): + event = { + "targetFunction": "", + "reservedConcurrency": 10 + } + + with pytest.raises(ValueError) as exc_info: + validate_event(event) + + assert str(exc_info.value) == "targetFunction is required" + mock_logger.error.assert_called_once_with("Missing required parameter: targetFunction") + + +def test_validate_event_reserved_concurrency_zero_is_valid(): + event = { + "targetFunction": "test-lambda-function", + "reservedConcurrency": 0 + } + + target_function, reserved_concurrency = validate_event(event) + + assert target_function == "test-lambda-function" + assert reserved_concurrency == 0 + + +def test_validate_event_with_additional_fields(): + event = { + "targetFunction": "test-lambda-function", + "reservedConcurrency": 10, + "extraField": "should-be-ignored" + } + + target_function, reserved_concurrency = validate_event(event) + + assert target_function == "test-lambda-function" + assert reserved_concurrency == 10 + + +def test_lambda_handler_service_raises_resource_not_found( + valid_event, context, mock_concurrency_controller_service +): + error_response = { + 'Error': { + 'Code': 'ResourceNotFoundException', + 'Message': 'Function not found' + } + } + + mock_concurrency_controller_service.update_function_concurrency.side_effect = ClientError( + error_response, 'PutFunctionConcurrency' + ) + + result = lambda_handler(valid_event, context) + + # The decorators convert exceptions to API Gateway error responses + assert result['statusCode'] == 500 + body = json.loads(result['body']) + assert body['message'] == 'Failed to utilise AWS client/resource' + assert body['err_code'] == 'GWY_5001' + + +def test_lambda_handler_service_raises_invalid_parameter( + context, mock_concurrency_controller_service +): + event = { + "targetFunction": "test-lambda-function", + "reservedConcurrency": -1 + } + + error_response = { + 'Error': { + 'Code': 'InvalidParameterValueException', + 'Message': 'Reserved concurrency value must be non-negative' + } + } + + mock_concurrency_controller_service.update_function_concurrency.side_effect = ClientError( + error_response, 'PutFunctionConcurrency' + ) + + result = lambda_handler(event, context) + + # The decorators convert exceptions to API Gateway error responses + assert result['statusCode'] == 500 + body = json.loads(result['body']) + assert body['message'] == 'Failed to utilise AWS client/resource' + assert body['err_code'] == 'GWY_5001' diff --git a/lambdas/tests/unit/services/test_concurrency_controller_service.py b/lambdas/tests/unit/services/test_concurrency_controller_service.py new file mode 100644 index 0000000000..ca1749472e --- /dev/null +++ b/lambdas/tests/unit/services/test_concurrency_controller_service.py @@ -0,0 +1,174 @@ +import pytest +from botocore.exceptions import ClientError +from services.concurrency_controller_service import ConcurrencyControllerService +from unittest.mock import MagicMock + + +@pytest.fixture +def mock_lambda_client(mocker): + return mocker.patch("services.concurrency_controller_service.boto3.client") + + +@pytest.fixture +def mock_logger(mocker): + return mocker.patch("services.concurrency_controller_service.logger") + + +@pytest.fixture +def service(mock_lambda_client): + return ConcurrencyControllerService() + + +def test_update_function_concurrency_success(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = 10 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + mock_client_instance.put_function_concurrency.return_value = { + "ReservedConcurrentExecutions": reserved_concurrency + } + + service.lambda_client = mock_client_instance + + result = service.update_function_concurrency(target_function, reserved_concurrency) + + mock_client_instance.put_function_concurrency.assert_called_once_with( + FunctionName=target_function, + ReservedConcurrentExecutions=reserved_concurrency + ) + + assert result["statusCode"] == 200 + assert result["body"]["message"] == "Concurrency updated successfully" + assert result["body"]["function"] == target_function + assert result["body"]["reservedConcurrency"] == reserved_concurrency + + mock_logger.info.assert_any_call( + f"Updating reserved concurrency for function '{target_function}' to {reserved_concurrency}" + ) + mock_logger.info.assert_any_call( + f"Successfully updated concurrency for '{target_function}'. " + f"Reserved concurrency set to: {reserved_concurrency}" + ) + + +def test_update_function_concurrency_function_not_found(service, mock_lambda_client, mock_logger): + target_function = "non-existent-function" + reserved_concurrency = 5 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + error_response = { + 'Error': { + 'Code': 'ResourceNotFoundException', + 'Message': 'Function not found' + } + } + + # Create proper exception classes that inherit from ClientError + resource_not_found_exception = type('ResourceNotFoundException', (ClientError,), {}) + invalid_param_exception = type('InvalidParameterValueException', (ClientError,), {}) + + # Create a mock exceptions object with both exception types + mock_exceptions = MagicMock() + mock_exceptions.ResourceNotFoundException = resource_not_found_exception + mock_exceptions.InvalidParameterValueException = invalid_param_exception + mock_client_instance.exceptions = mock_exceptions + + mock_client_instance.put_function_concurrency.side_effect = resource_not_found_exception( + error_response, 'PutFunctionConcurrency' + ) + + service.lambda_client = mock_client_instance + + with pytest.raises(ClientError): + service.update_function_concurrency(target_function, reserved_concurrency) + + mock_logger.error.assert_called_once_with( + f"Lambda function '{target_function}' not found" + ) + + +def test_update_function_concurrency_invalid_parameter(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = -1 # Invalid value + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + error_response = { + 'Error': { + 'Code': 'InvalidParameterValueException', + 'Message': 'Reserved concurrency value must be non-negative' + } + } + + # Create proper exception classes that inherit from ClientError + resource_not_found_exception = type('ResourceNotFoundException', (ClientError,), {}) + invalid_param_exception = type('InvalidParameterValueException', (ClientError,), {}) + + # Create a mock exceptions object with both exception types + mock_exceptions = MagicMock() + mock_exceptions.ResourceNotFoundException = resource_not_found_exception + mock_exceptions.InvalidParameterValueException = invalid_param_exception + mock_client_instance.exceptions = mock_exceptions + + mock_client_instance.put_function_concurrency.side_effect = invalid_param_exception( + error_response, 'PutFunctionConcurrency' + ) + + service.lambda_client = mock_client_instance + + with pytest.raises(ClientError): + service.update_function_concurrency(target_function, reserved_concurrency) + + assert mock_logger.error.call_count == 1 + error_call_args = str(mock_logger.error.call_args) + assert "Invalid concurrency value" in error_call_args + + +def test_update_function_concurrency_with_zero_value(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = 0 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + mock_client_instance.put_function_concurrency.return_value = { + "ReservedConcurrentExecutions": reserved_concurrency + } + + service.lambda_client = mock_client_instance + + result = service.update_function_concurrency(target_function, reserved_concurrency) + + assert result["statusCode"] == 200 + assert result["body"]["reservedConcurrency"] == 0 + + +def test_update_function_concurrency_with_large_value(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = 1000 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + mock_client_instance.put_function_concurrency.return_value = { + "ReservedConcurrentExecutions": reserved_concurrency + } + + service.lambda_client = mock_client_instance + + result = service.update_function_concurrency(target_function, reserved_concurrency) + + assert result["statusCode"] == 200 + assert result["body"]["reservedConcurrency"] == 1000 + + +def test_init_creates_lambda_client(mock_lambda_client): + service = ConcurrencyControllerService() + + mock_lambda_client.assert_called_once_with("lambda") + assert service.lambda_client is not None From edb45279853ac3d5340875f07f2759ff0190d13c Mon Sep 17 00:00:00 2001 From: SWhyteAnswer Date: Mon, 22 Dec 2025 15:29:33 +0000 Subject: [PATCH 3/4] [PRMP-1048] seperating handler from serbice --- .../concurrency_controller_handler.py | 11 ++- .../concurrency_controller_service.py | 32 ++++--- .../test_concurrency_controller_handler.py | 52 +++++------ .../test_concurrency_controller_service.py | 89 +++++++++++-------- 4 files changed, 103 insertions(+), 81 deletions(-) diff --git a/lambdas/handlers/concurrency_controller_handler.py b/lambdas/handlers/concurrency_controller_handler.py index c9cce11992..864120548a 100644 --- a/lambdas/handlers/concurrency_controller_handler.py +++ b/lambdas/handlers/concurrency_controller_handler.py @@ -29,4 +29,13 @@ def lambda_handler(event, _context): target_function, reserved_concurrency = validate_event(event) service = ConcurrencyControllerService() - return service.update_function_concurrency(target_function, reserved_concurrency) + updated_concurrency = service.update_function_concurrency(target_function, reserved_concurrency) + + return { + "statusCode": 200, + "body": { + "message": "Concurrency updated successfully", + "function": target_function, + "reservedConcurrency": updated_concurrency + } + } diff --git a/lambdas/services/concurrency_controller_service.py b/lambdas/services/concurrency_controller_service.py index 3ea1e97b08..4efe49a321 100644 --- a/lambdas/services/concurrency_controller_service.py +++ b/lambdas/services/concurrency_controller_service.py @@ -1,4 +1,5 @@ import boto3 +from botocore.exceptions import ClientError from utils.audit_logging_setup import LoggingService logger = LoggingService(__name__) @@ -21,22 +22,27 @@ def update_function_concurrency(self, target_function, reserved_concurrency): updated_concurrency = response.get("ReservedConcurrentExecutions") + if updated_concurrency is None: + logger.error("Response did not contain ReservedConcurrentExecutions") + raise ValueError("Failed to confirm concurrency update from AWS response") + + if updated_concurrency != reserved_concurrency: + logger.error( + f"Concurrency mismatch: requested {reserved_concurrency}, " + f"AWS returned {updated_concurrency}" + ) + raise ValueError("Concurrency update verification failed") + logger.info( f"Successfully updated concurrency for '{target_function}'. " f"Reserved concurrency set to: {updated_concurrency}" ) - return { - "statusCode": 200, - "body": { - "message": "Concurrency updated successfully", - "function": target_function, - "reservedConcurrency": updated_concurrency - } - } - except self.lambda_client.exceptions.ResourceNotFoundException: - logger.error(f"Lambda function '{target_function}' not found") - raise - except self.lambda_client.exceptions.InvalidParameterValueException as e: - logger.error(f"Invalid concurrency value: {e}") + return updated_concurrency + except ClientError as e: + error_code = e.response.get("Error", {}).get("Code", "") + if error_code == "ResourceNotFoundException": + logger.error(f"Lambda function '{target_function}' not found") + else: + logger.error(f"Failed to update concurrency: {str(e)}") raise diff --git a/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py index 051e8d6d44..ed32733606 100644 --- a/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py +++ b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py @@ -36,16 +36,7 @@ def event_with_zero_concurrency(): def test_lambda_handler_success(valid_event, context, mock_concurrency_controller_service): - expected_response = { - "statusCode": 200, - "body": { - "message": "Concurrency updated successfully", - "function": "test-lambda-function", - "reservedConcurrency": 10 - } - } - - mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + mock_concurrency_controller_service.update_function_concurrency.return_value = 10 result = lambda_handler(valid_event, context) @@ -53,22 +44,16 @@ def test_lambda_handler_success(valid_event, context, mock_concurrency_controlle "test-lambda-function", 10 ) - assert result == expected_response + assert result["statusCode"] == 200 + assert result["body"]["message"] == "Concurrency updated successfully" + assert result["body"]["function"] == "test-lambda-function" + assert result["body"]["reservedConcurrency"] == 10 def test_lambda_handler_with_zero_concurrency( event_with_zero_concurrency, context, mock_concurrency_controller_service ): - expected_response = { - "statusCode": 200, - "body": { - "message": "Concurrency updated successfully", - "function": "test-lambda-function", - "reservedConcurrency": 0 - } - } - - mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + mock_concurrency_controller_service.update_function_concurrency.return_value = 0 result = lambda_handler(event_with_zero_concurrency, context) @@ -76,7 +61,10 @@ def test_lambda_handler_with_zero_concurrency( "test-lambda-function", 0 ) - assert result == expected_response + assert result["statusCode"] == 200 + assert result["body"]["message"] == "Concurrency updated successfully" + assert result["body"]["function"] == "test-lambda-function" + assert result["body"]["reservedConcurrency"] == 0 def test_lambda_handler_with_large_concurrency(context, mock_concurrency_controller_service): @@ -85,16 +73,7 @@ def test_lambda_handler_with_large_concurrency(context, mock_concurrency_control "reservedConcurrency": 1000 } - expected_response = { - "statusCode": 200, - "body": { - "message": "Concurrency updated successfully", - "function": "test-lambda-function", - "reservedConcurrency": 1000 - } - } - - mock_concurrency_controller_service.update_function_concurrency.return_value = expected_response + mock_concurrency_controller_service.update_function_concurrency.return_value = 1000 result = lambda_handler(event, context) @@ -102,6 +81,15 @@ def test_lambda_handler_with_large_concurrency(context, mock_concurrency_control "test-lambda-function", 1000 ) + assert result["statusCode"] == 200 + assert result["body"]["message"] == "Concurrency updated successfully" + assert result["body"]["function"] == "test-lambda-function" + assert result["body"]["reservedConcurrency"] == 1000 + + mock_concurrency_controller_service.update_function_concurrency.assert_called_once_with( + "test-lambda-function", 1000 + ) + assert result == expected_response diff --git a/lambdas/tests/unit/services/test_concurrency_controller_service.py b/lambdas/tests/unit/services/test_concurrency_controller_service.py index ca1749472e..47e9da153e 100644 --- a/lambdas/tests/unit/services/test_concurrency_controller_service.py +++ b/lambdas/tests/unit/services/test_concurrency_controller_service.py @@ -39,10 +39,7 @@ def test_update_function_concurrency_success(service, mock_lambda_client, mock_l ReservedConcurrentExecutions=reserved_concurrency ) - assert result["statusCode"] == 200 - assert result["body"]["message"] == "Concurrency updated successfully" - assert result["body"]["function"] == target_function - assert result["body"]["reservedConcurrency"] == reserved_concurrency + assert result == reserved_concurrency mock_logger.info.assert_any_call( f"Updating reserved concurrency for function '{target_function}' to {reserved_concurrency}" @@ -64,20 +61,11 @@ def test_update_function_concurrency_function_not_found(service, mock_lambda_cli 'Error': { 'Code': 'ResourceNotFoundException', 'Message': 'Function not found' - } + }, + 'ResponseMetadata': {'HTTPStatusCode': 404} } - # Create proper exception classes that inherit from ClientError - resource_not_found_exception = type('ResourceNotFoundException', (ClientError,), {}) - invalid_param_exception = type('InvalidParameterValueException', (ClientError,), {}) - - # Create a mock exceptions object with both exception types - mock_exceptions = MagicMock() - mock_exceptions.ResourceNotFoundException = resource_not_found_exception - mock_exceptions.InvalidParameterValueException = invalid_param_exception - mock_client_instance.exceptions = mock_exceptions - - mock_client_instance.put_function_concurrency.side_effect = resource_not_found_exception( + mock_client_instance.put_function_concurrency.side_effect = ClientError( error_response, 'PutFunctionConcurrency' ) @@ -102,20 +90,11 @@ def test_update_function_concurrency_invalid_parameter(service, mock_lambda_clie 'Error': { 'Code': 'InvalidParameterValueException', 'Message': 'Reserved concurrency value must be non-negative' - } + }, + 'ResponseMetadata': {'HTTPStatusCode': 400} } - # Create proper exception classes that inherit from ClientError - resource_not_found_exception = type('ResourceNotFoundException', (ClientError,), {}) - invalid_param_exception = type('InvalidParameterValueException', (ClientError,), {}) - - # Create a mock exceptions object with both exception types - mock_exceptions = MagicMock() - mock_exceptions.ResourceNotFoundException = resource_not_found_exception - mock_exceptions.InvalidParameterValueException = invalid_param_exception - mock_client_instance.exceptions = mock_exceptions - - mock_client_instance.put_function_concurrency.side_effect = invalid_param_exception( + mock_client_instance.put_function_concurrency.side_effect = ClientError( error_response, 'PutFunctionConcurrency' ) @@ -124,9 +103,9 @@ def test_update_function_concurrency_invalid_parameter(service, mock_lambda_clie with pytest.raises(ClientError): service.update_function_concurrency(target_function, reserved_concurrency) - assert mock_logger.error.call_count == 1 - error_call_args = str(mock_logger.error.call_args) - assert "Invalid concurrency value" in error_call_args + mock_logger.error.assert_called_once_with( + f"Failed to update concurrency: An error occurred (InvalidParameterValueException) when calling the PutFunctionConcurrency operation: Reserved concurrency value must be non-negative" + ) def test_update_function_concurrency_with_zero_value(service, mock_lambda_client, mock_logger): @@ -144,8 +123,7 @@ def test_update_function_concurrency_with_zero_value(service, mock_lambda_client result = service.update_function_concurrency(target_function, reserved_concurrency) - assert result["statusCode"] == 200 - assert result["body"]["reservedConcurrency"] == 0 + assert result == 0 def test_update_function_concurrency_with_large_value(service, mock_lambda_client, mock_logger): @@ -163,8 +141,7 @@ def test_update_function_concurrency_with_large_value(service, mock_lambda_clien result = service.update_function_concurrency(target_function, reserved_concurrency) - assert result["statusCode"] == 200 - assert result["body"]["reservedConcurrency"] == 1000 + assert result == 1000 def test_init_creates_lambda_client(mock_lambda_client): @@ -172,3 +149,45 @@ def test_init_creates_lambda_client(mock_lambda_client): mock_lambda_client.assert_called_once_with("lambda") assert service.lambda_client is not None + + +def test_update_function_concurrency_missing_response_field(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = 10 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + # Response missing ReservedConcurrentExecutions field + mock_client_instance.put_function_concurrency.return_value = {} + + service.lambda_client = mock_client_instance + + with pytest.raises(ValueError) as exc_info: + service.update_function_concurrency(target_function, reserved_concurrency) + + assert str(exc_info.value) == "Failed to confirm concurrency update from AWS response" + mock_logger.error.assert_called_with("Response did not contain ReservedConcurrentExecutions") + + +def test_update_function_concurrency_value_mismatch(service, mock_lambda_client, mock_logger): + target_function = "test-lambda-function" + reserved_concurrency = 10 + + mock_client_instance = MagicMock() + mock_lambda_client.return_value = mock_client_instance + + # AWS returned different value than requested + mock_client_instance.put_function_concurrency.return_value = { + "ReservedConcurrentExecutions": 5 + } + + service.lambda_client = mock_client_instance + + with pytest.raises(ValueError) as exc_info: + service.update_function_concurrency(target_function, reserved_concurrency) + + assert str(exc_info.value) == "Concurrency update verification failed" + mock_logger.error.assert_called_with( + f"Concurrency mismatch: requested {reserved_concurrency}, AWS returned 5" + ) From e194ff4ce54f9fc44e87ca80b21bc1dcccb19379 Mon Sep 17 00:00:00 2001 From: SWhyteAnswer Date: Tue, 23 Dec 2025 11:03:35 +0000 Subject: [PATCH 4/4] [PRMP-1048] fixing tests --- .../unit/handlers/test_concurrency_controller_handler.py | 6 ------ .../unit/services/test_concurrency_controller_service.py | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py index ed32733606..08c4e35650 100644 --- a/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py +++ b/lambdas/tests/unit/handlers/test_concurrency_controller_handler.py @@ -85,12 +85,6 @@ def test_lambda_handler_with_large_concurrency(context, mock_concurrency_control assert result["body"]["message"] == "Concurrency updated successfully" assert result["body"]["function"] == "test-lambda-function" assert result["body"]["reservedConcurrency"] == 1000 - - mock_concurrency_controller_service.update_function_concurrency.assert_called_once_with( - "test-lambda-function", 1000 - ) - - assert result == expected_response def test_validate_event_success(valid_event): diff --git a/lambdas/tests/unit/services/test_concurrency_controller_service.py b/lambdas/tests/unit/services/test_concurrency_controller_service.py index 47e9da153e..ded2cb3fcd 100644 --- a/lambdas/tests/unit/services/test_concurrency_controller_service.py +++ b/lambdas/tests/unit/services/test_concurrency_controller_service.py @@ -104,7 +104,8 @@ def test_update_function_concurrency_invalid_parameter(service, mock_lambda_clie service.update_function_concurrency(target_function, reserved_concurrency) mock_logger.error.assert_called_once_with( - f"Failed to update concurrency: An error occurred (InvalidParameterValueException) when calling the PutFunctionConcurrency operation: Reserved concurrency value must be non-negative" + f"Failed to update concurrency: An error occurred (InvalidParameterValueException) when calling the " + f"PutFunctionConcurrency operation: Reserved concurrency value must be non-negative" )