# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

'''Lambda functions often use the provided context object that provide 
information about the invocation, function, and execution environment. 
The context object, which is normally passed into the lambda function by 
default, needs to be mocked when testing lambda functions.
NOTE: You only need to mock the context object for testing if it is called 
inside your Lambda function'''

import pytest
import logging
import sys
import os
import pathlib
import uuid
from unittest.mock import patch
# discover the src directory to import the file being tested
parent_dir = pathlib.Path(__file__).parent.resolve()
src_path = os.path.join(parent_dir, '..', 'src')
sys.path.append(src_path)
from lambda_function_context import lambda_handler

# setup logging to terminal
level = logging.DEBUG
logger = logging.getLogger(__name__)
logger.setLevel(level)
ch = logging.StreamHandler()
ch.setLevel(level)
logger.addHandler(ch)

# an empty class to mock
class context:
    pass

# demonstrate mocking lambda context methods/properties for unit testing
class TestLambdaHandler:
    # normally, calling the lambda function that uses the context object locally will result in an error
    def test_lambda_handler_context_error(self):
        logger.debug('Demonstrating calling lambda function without mocking the context object will result in an error')
        with pytest.raises(Exception) as execinfo:
            lambda_handler({}, context())
        # get the raised exception message back
        result = execinfo.value.args[0]
        expected = '\'context\' object has no attribute'
        logger.debug('Result: {}'.format(result))
        logger.debug('Expected: {}'.format(expected))
        # check the mocked exception message was received
        assert expected in result

    '''Mock the context object to test the lambda function locally. Context functions/properties are not 
    limited to what is shown below. Reference the AWS Lambda developer guide for the full list of 
    context methods/properties.'''
    def test_invoked_function_arn(self):
        logger.debug('Testing ability to mock lambda context object')
        # set the return values to mock
        arn = 'arn:aws:lambda:us-east-1:accountid:function:context'
        log_stream = 'date[$LATEST]id'
        log_group = '/aws/lambda/context'
        request_id = str(uuid.uuid1())
        memory_limit = 128
        remaining_time = 2999
        # mock the context object and set its attributes
        with patch('test_lambda_function_context.context') as mock_context:
            instance = mock_context.return_value
            # instance variables
            instance.invoked_function_arn = arn
            instance.log_stream_name = log_stream
            instance.log_group_name = log_group
            instance.aws_request_id = request_id
            instance.memory_limit_in_mb = memory_limit
            # instance functions: use .return_value for functions
            instance.get_remaining_time_in_millis.return_value = remaining_time
            result = lambda_handler({}, context())
        # check the mocked values were returned
        expected = (arn, log_stream, log_group, request_id, memory_limit, remaining_time)
        logger.debug('Result: {}'.format(result))
        logger.debug('Expected: {}'.format(expected))
        assert result == expected