# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import os from unittest import TestCase from unittest.mock import patch, MagicMock with patch.dict(os.environ, { 'ACHIEVEMENTS_TABLE_NAME': 'gamekit_dev_foogamename_game_achievements', 'PLAYER_ACHIEVEMENTS_TABLE_NAME': 'gamekit_dev_foogamename_player_achievements' }) as env_mock: with patch("boto3.client") as boto_resource_mock: with patch("gamekithelpers.ddb.get_table") as layer_boto_mock: from functions.achievements.UpdateAchievements import index class TestIndex(TestCase): @patch('functions.achievements.UpdateAchievements.index.ddb.boto3') def setUp(self, mock_boto3: MagicMock): index.ddb_client = MagicMock() index.ddb_game_table = mock_boto3.resource('dynamodb').Table('test_table') index.ddb_player_table = mock_boto3.resource('dynamodb').Table('test_player_table') def test_lambda_returns_a_400_error_code_when_body_is_empty(self): # Arrange event = self.get_lambda_event() event['body'] = None # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_400_error_code_when_increment_by_key_is_missing(self): # Arrange event = self.get_lambda_event() event['body'] = '{}' # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_400_error_code_when_increment_by_key_is_zero(self): # Arrange event = self.get_lambda_event() event['body'] = '{"increment_by": 0}' # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_400_error_code_when_increment_by_key_is_less_than_zero(self): # Arrange event = self.get_lambda_event() event['body'] = '{"increment_by": -1}' # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_400_error_code_when_path_parameters_is_empty(self): # Arrange event = self.get_lambda_event() event['pathParameters'] = {} # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_400_error_code_when_achievement_id_is_empty(self): # Arrange event = self.get_lambda_event() event['pathParameters'] = {'achievement_id': None} # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(400, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_401_error_code_when_player_id_is_empty(self): # Arrange event = self.get_lambda_event() event['requestContext'] = {'authorizer': {'claims': {}}} # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(401, result['statusCode']) self.assert_did_not_call_dynamodb(index.ddb_game_table) def test_lambda_returns_a_200_success_code_when_incrementing(self): # Arrange event = self.get_lambda_event() index.ddb_game_table.get_item.return_value = self.mocked_get_achievement_result() index.ddb_player_table.update_item.return_value = self.update_player_achievement_result() # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(200, result['statusCode']) index.ddb_player_table.get_item.assert_called_once() self.assertEqual(index.ddb_player_table.update_item.call_count, 2) def test_lambda_returns_a_404_error_code_when_incrementing_hidden_achievement(self): # Arrange event = self.get_lambda_event() index.ddb_game_table.get_item.return_value = self.mocked_get_hidden_achievement_result() index.ddb_player_table.update_item.return_value = self.update_player_achievement_result() # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(404, result['statusCode']) index.ddb_player_table.get_item.assert_called_once() self.assertEqual(index.ddb_player_table.update_item.call_count, 0) def test_lambda_returns_a_404_error_code_when_incrementing_non_existent_achievement(self): # Arrange event = self.get_lambda_event() index.ddb_game_table.get_item.return_value = self.mocked_get_empty_achievement_result() # Act result = index.lambda_handler(event, None) # Assert self.assertEqual(404, result['statusCode']) @staticmethod def get_lambda_event(): return { 'resource': '/achievements/{achievement_id}/unlock', 'path': '/achievements/EAT_THOUSAND_BANANAS/unlock', 'httpMethod': 'POST', 'headers': { 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate, br', 'Authorization': 'Bearer jwt_token', 'Content-Type': 'application/json', 'Host': 'abcdefghij.execute-api.us-west-2.amazonaws.com', 'User-Agent': 'TestAgent', 'X-Amzn-Trace-Id': 'Root=1-6100c5a0-01be676050fa5fa93ed1e308', 'X-Forwarded-For': '127.0.0.1', 'X-Forwarded-Port': '443', 'X-Forwarded-Proto': 'https' }, 'multiValueHeaders': { 'Accept': ['*/*'], 'Accept-Encoding': ['gzip, deflate, br'], 'Authorization': ['Bearer jwt_token'], 'Content-Type': ['application/json'], 'Host': ['abcdefghij.execute-api.us-west-2.amazonaws.com'], 'User-Agent': ['TestAgent'], 'X-Amzn-Trace-Id': ['Root=1-6100c5a0-01be676050fa5fa93ed1e308'], 'X-Forwarded-For': ['127.0.0.1'], 'X-Forwarded-Port': ['443'], 'X-Forwarded-Proto': ['https'] }, 'queryStringParameters': None, 'multiValueQueryStringParameters': None, 'pathParameters': { 'achievement_id': 'EAT_THOUSAND_BANANAS' }, 'stageVariables': None, 'requestContext': { 'resourceId': 'abcdef', 'authorizer': { 'claims': { 'sub': '12345678-1234-1234-1234-123456789012', 'iss': 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_123456789', 'cognito:username': 'jrnic', 'origin_jti': '12345678-1234-1234-1234-123456789012', 'aud': '7s24tlabcn8n0defbfoghijsgn', 'event_id': '6234d920-b637-4cdf-bd44-3a5e53f51569', 'token_use': 'id', 'auth_time': '1627438909', 'custom:gk_user_id': '12345678-1234-1234-1234-123456789012', 'exp': 'Wed Jul 28 03:21:49 UTC 2021', 'iat': 'Wed Jul 28 02:21:49 UTC 2021', 'jti': '7s24tlabcn8n0defbfoghijsgn', 'email': 'xyz@abc.def' } }, 'resourcePath': '/achievements/{achievement_id}/unlock', 'httpMethod': 'POST', 'extendedRequestId': 'DKPRHHxmPHcFlXA=', 'requestTime': '28/Jul/2021:02:49:04 +0000', 'path': '/dev/achievements/EAT_THOUSAND_BANANAS/unlock', 'accountId': '123456789012', 'protocol': 'HTTP/1.1', 'stage': 'dev', 'domainPrefix': 'abcdefghij', 'requestTimeEpoch': 1627440544542, 'requestId': '1693ff18-4aed-438b-bb01-0929bc9e640d', 'identity': { 'cognitoIdentityPoolId': None, 'accountId': None, 'cognitoIdentityId': None, 'caller': None, 'sourceIp': '127.0.0.1', 'principalOrgId': None, 'accessKey': None, 'cognitoAuthenticationType': None, 'cognitoAuthenticationProvider': None, 'userArn': None, 'userAgent': 'TestAgent', 'user': None }, 'domainName': 'abcdefghij.execute-api.us-west-2.amazonaws.com', 'apiId': 'abcdefghij' }, 'body': '{"increment_by": 1}', 'isBase64Encoded': False } @staticmethod def mocked_get_achievement_result(): return { 'Item': { 'created_at': '2021-07-27T00:38:42.927905+00:00', 'locked_description': 'Eat 1,000 bananas', 'achievement_id': 'EAT_THOUSAND_BANANAS', 'unlocked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_unlocked.png', 'max_value': 1001, 'unlocked_description': 'You ate 1,000 bananas!', 'is_secret': False, 'locked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_locked.png', 'updated_at': '2021-07-27T16:54:29.130692+00:00', 'is_stateful': True, 'points': 10, 'order_number': 1, 'is_hidden': False, 'title': 'Hangry Chicken' } } @staticmethod def mocked_get_empty_achievement_result(): # Note - For a real empty result there would be metadata here, but that is not needed for testing return {} @staticmethod def mocked_get_secret_achievement_result(): return { 'Item': { 'created_at': '2021-07-27T00:38:42.927905+00:00', 'locked_description': 'Eat 1,000 bananas', 'achievement_id': 'EAT_THOUSAND_BANANAS', 'unlocked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_unlocked.png', 'max_value': 1001, 'unlocked_description': 'You ate 1,000 bananas!', 'is_secret': True, 'locked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_locked.png', 'updated_at': '2021-07-27T16:54:29.130692+00:00', 'is_stateful': True, 'points': 10, 'order_number': 1, 'is_hidden': False, 'title': 'Hangry Chicken' } } @staticmethod def mocked_get_hidden_achievement_result(): return { 'Item': { 'created_at': '2021-07-27T00:38:42.927905+00:00', 'locked_description': 'Eat 1,000 bananas', 'achievement_id': 'EAT_THOUSAND_BANANAS', 'unlocked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_unlocked.png', 'max_value': 1001, 'unlocked_description': 'You ate 1,000 bananas!', 'is_secret': False, 'locked_icon_url': 'https://mygame.cloudfront.net/achievements/icon/1_locked.png', 'updated_at': '2021-07-27T16:54:29.130692+00:00', 'is_stateful': True, 'points': 10, 'order_number': 1, 'is_hidden': True, 'title': 'Hangry Chicken' } } @staticmethod def update_player_achievement_result(): return { 'Attributes': { 'updated_at': '2021-07-28T03:37:37.267711+00:00', 'created_at': '2021-07-28T03:37:32.227830+00:00', 'earned': True, 'achievement_id': 'EAT_THOUSAND_BANANAS', 'current_value': 5, 'player_id': '12345678-1234-1234-1234-123456789012', 'earned_at': '2021-07-28T03:37:37.267711+00:00' } } @staticmethod def mocked_get_player_achievement_result(): return { 'updated_at': '2021-07-28T03:37:37.267711+00:00', 'created_at': '2021-07-28T03:37:32.227830+00:00', 'earned': True, 'achievement_id': 'EAT_THOUSAND_BANANAS', 'current_value': 5, 'player_id': '12345678-1234-1234-1234-123456789012', 'earned_at': '2021-07-28T03:37:37.267711+00:00' } @staticmethod def assert_did_not_call_dynamodb(mock_dynamodb): mock_dynamodb.get_item.assert_not_called() mock_dynamodb.update_item.assert_not_called()