Skip to content
This repository was archived by the owner on Nov 3, 2025. It is now read-only.

Commit 50d1b5f

Browse files
brandoldDold
andauthored
API & Manager Unit tests (#175)
* unit tests for api and manager lambda * update coverage report to use xml and add sonar project file Co-authored-by: Dold <[email protected]>
1 parent 04a01d6 commit 50d1b5f

File tree

10 files changed

+905
-21
lines changed

10 files changed

+905
-21
lines changed

sonar-project.properties

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Uncomment to enable debugging by default
2+
#sonar.verbose=true
3+
#sonar.log.level=DEBUG
4+
5+
# Disable if needed
6+
#sonar.scm.disabled=true
7+
8+
#
9+
# Refer to https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/
10+
# for details on sources and exclusions. Note also .gitignore
11+
#
12+
13+
sonar.sources= \
14+
source/api
15+
16+
17+
# sonar.exclusions=
18+
19+
# sonar.tests=source/
20+
21+
sonar.sourceEncoding=UTF-8
22+
23+
## Python Specific Properties*
24+
# coverage
25+
# https://docs.sonarqube.org/pages/viewpage.action?pageId=4784149
26+
# Comma-separated list of ant pattern describing paths to coverage reports, relative to projects
27+
# root. Leave unset to use the default ("coverage-reports/*coverage-*.xml").
28+
29+
sonar.python.coverage.reportPaths= \
30+
test/unit/coverage.xml

source/api/app.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -548,14 +548,14 @@ def list_objects(filesystem_id):
548548
:returns: Filesystem operation response
549549
:raises ChaliceViewError, BadRequestError
550550
"""
551-
if app.current_request.query_params['path']:
551+
try:
552552
path = app.current_request.query_params['path']
553+
except KeyError as error:
554+
app.log.error('Missing required query param: {e}'.format(e=error))
555+
raise BadRequestError('Missing required query param: {e}'.format(e=error))
553556
else:
554-
app.log.error('Missing required query param: path')
555-
raise BadRequestError('Missing required query param: path')
556-
557-
filemanager_event = {"operation": "list", "path": path}
558-
operation_result = proxy_operation_to_efs_lambda(filesystem_id, filemanager_event)
559-
error_message = "Error listing files"
557+
filemanager_event = {"operation": "list", "path": path}
558+
operation_result = proxy_operation_to_efs_lambda(filesystem_id, filemanager_event)
559+
error_message = "Error listing files"
560560

561-
return format_operation_response(operation_result, error_message)
561+
return format_operation_response(operation_result, error_message)

source/api/chalicelib/efs_lambda.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,13 @@
1212
######################################################################################################################
1313

1414
import os
15-
from os import walk
1615
import base64
1716
import math
1817
# File manager operation events:
1918
# list: {"operation": "list", "path": "$dir"}
2019
# upload: {"operation": "upload", "path": "$dir", "form_data": "$form_data"}
2120

2221

23-
def modify(event):
24-
return None
25-
26-
2722
def delete(event):
2823
print(event)
2924
path = event['path']
@@ -140,20 +135,18 @@ def list(event):
140135
try:
141136
path = event['path']
142137
except KeyError:
143-
raise Exception('Missing required parameter in event: "path"')
138+
return {"message": "missing required parameter: path", "statusCode": 400}
144139

145140
try:
146-
# TODO: Mucchhhh better thinking around listing directories
147141
dir_items = []
148142
file_items = []
149-
for (dirpath, dirnames, filenames) in walk(path):
143+
for (dirpath, dirnames, filenames) in os.walk(path):
150144
dir_items.extend(dirnames)
151145
file_items.extend(filenames)
152146
break
153-
# TODO: narrower exception scope and proper debug output
154147
except Exception as error:
155148
print(error)
156-
raise Exception(error)
149+
return {"message": "unable to list files", "statusCode": 500}
157150
else:
158151
return {"path": path, "directiories": dir_items, "files": file_items, "statusCode": 200}
159152

@@ -163,16 +156,14 @@ def lambda_handler(event, context):
163156
try:
164157
operation_type = event['operation']
165158
except KeyError:
166-
raise Exception('Missing required parameter in event: "operation"')
159+
return {"message": "missing required parameter: operation", "statusCode": 400}
167160
else:
168161
if operation_type == 'upload':
169162
upload_result = upload(event)
170163
return upload_result
171164
if operation_type == 'list':
172165
list_result = list(event)
173166
return list_result
174-
if operation_type == 'modify':
175-
modify(event)
176167
if operation_type == 'delete':
177168
delete_result = delete(event)
178169
return delete_result

test/unit/api/conftest.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from chalice.test import Client
3+
from botocore.stub import Stubber
4+
5+
@pytest.fixture(autouse=True)
6+
def mock_env_variables(monkeypatch):
7+
monkeypatch.syspath_prepend('../../source/api/')
8+
monkeypatch.setenv("stackPrefix", "testStackPrefix")
9+
monkeypatch.setenv("botoConfig", '{"user_agent_extra": "AwsSolution/SO0145/vX.X.X"}')
10+
11+
@pytest.fixture
12+
def test_client(mock_env_variables):
13+
from app import app
14+
with Client(app) as client:
15+
yield client
16+
17+
@pytest.fixture
18+
def efs_client_stub(mock_env_variables):
19+
from app import EFS
20+
with Stubber(EFS) as stubber:
21+
yield stubber
22+
stubber.assert_no_pending_responses()
23+
24+
@pytest.fixture
25+
def cfn_client_stub(mock_env_variables):
26+
from app import CFN
27+
with Stubber(CFN) as stubber:
28+
yield stubber
29+
stubber.assert_no_pending_responses()
30+
31+
@pytest.fixture
32+
def lambda_client_stub(mock_env_variables):
33+
from app import SERVERLESS
34+
with Stubber(SERVERLESS) as stubber:
35+
yield stubber
36+
stubber.assert_no_pending_responses()

test/unit/api/service_responses.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import json
2+
import io
3+
from datetime import datetime
4+
5+
test_filesystem_id = 'fs-01234567'
6+
7+
test_stack_name = f'testStackPrefix-ManagedResources-{test_filesystem_id}'
8+
9+
efs_describe_file_systems_no_marker_response = {
10+
'FileSystems': [
11+
{
12+
'CreationTime': datetime.now(),
13+
'CreationToken': 'tokenstring',
14+
'FileSystemId': f'{test_filesystem_id}',
15+
'LifeCycleState': 'available',
16+
'Name': 'MyFileSystem',
17+
'NumberOfMountTargets': 1,
18+
'OwnerId': '012345678912',
19+
'PerformanceMode': 'generalPurpose',
20+
'SizeInBytes': {
21+
'Value': 6144,
22+
},
23+
'Tags': [
24+
{
25+
'Key': 'Name',
26+
'Value': 'MyFileSystem',
27+
},
28+
],
29+
},
30+
],
31+
'ResponseMetadata': {
32+
'...': '...',
33+
},
34+
}
35+
36+
efs_describe_mount_targets_response = {
37+
'MountTargets': [
38+
{
39+
'FileSystemId': 'fs-01234567',
40+
'IpAddress': '192.0.0.2',
41+
'LifeCycleState': 'available',
42+
'MountTargetId': 'fsmt-12340abc',
43+
'NetworkInterfaceId': 'eni-cedf6789',
44+
'OwnerId': '012345678912',
45+
'SubnetId': 'subnet-1234abcd',
46+
},
47+
],
48+
'ResponseMetadata': {
49+
'...': '...',
50+
},
51+
}
52+
53+
efs_describe_mount_target_security_groups_response = {
54+
'SecurityGroups': [
55+
'sg-4567abcd',
56+
],
57+
'ResponseMetadata': {
58+
'...': '...',
59+
},
60+
}
61+
62+
cfn_describe_stacks_response = {
63+
'Stacks': [
64+
{
65+
'StackId': 'string',
66+
'StackName': test_stack_name,
67+
'ChangeSetId': 'string',
68+
'Description': 'string',
69+
'Parameters': [
70+
{
71+
'ParameterKey': 'string',
72+
'ParameterValue': 'string',
73+
'UsePreviousValue': True,
74+
'ResolvedValue': 'string'
75+
},
76+
],
77+
'CreationTime': datetime.now(),
78+
'DeletionTime': datetime.now(),
79+
'LastUpdatedTime': datetime.now(),
80+
'RollbackConfiguration': {
81+
'RollbackTriggers': [
82+
{
83+
'Arn': 'string',
84+
'Type': 'string'
85+
},
86+
],
87+
'MonitoringTimeInMinutes': 123
88+
},
89+
'StackStatus': 'CREATE_COMPLETE',
90+
'StackStatusReason': 'string',
91+
'DisableRollback': True,
92+
'NotificationARNs': [
93+
'string',
94+
],
95+
'TimeoutInMinutes': 123,
96+
'Capabilities': [
97+
'CAPABILITY_IAM'
98+
],
99+
'Outputs': [
100+
{
101+
'OutputKey': 'string',
102+
'OutputValue': 'string',
103+
'Description': 'string',
104+
'ExportName': 'string'
105+
},
106+
],
107+
'RoleARN': 'arn:aws:iam::xxxxxxx:role/xxxxx',
108+
'Tags': [
109+
{
110+
'Key': 'string',
111+
'Value': 'string'
112+
},
113+
],
114+
'EnableTerminationProtection': False,
115+
'ParentId': 'string',
116+
'RootId': 'string',
117+
'DriftInformation': {
118+
'StackDriftStatus': 'IN_SYNC',
119+
'LastCheckTimestamp': datetime.now()
120+
}
121+
},
122+
],
123+
'NextToken': 'string'
124+
}
125+
126+
cfn_create_stack_response = {
127+
'StackId': test_stack_name
128+
}
129+
130+
lambda_invoke_upload_response = {
131+
'StatusCode': 200,
132+
'FunctionError': 'string',
133+
'LogResult': 'string',
134+
'Payload': io.BytesIO(bytes(json.dumps({"message": "Chunk upload successful", "statusCode": 200}), 'utf-8')),
135+
'ExecutedVersion': 'string'
136+
}
137+
138+
lambda_invoke_delete_response = {
139+
'StatusCode': 200,
140+
'FunctionError': 'string',
141+
'LogResult': 'string',
142+
'Payload': io.BytesIO(bytes(json.dumps({"message": "file deletion successful", "statusCode": 200}), 'utf-8')),
143+
'ExecutedVersion': 'string'
144+
}
145+
146+
lambda_invoke_list_response = {
147+
'StatusCode': 200,
148+
'FunctionError': 'string',
149+
'LogResult': 'string',
150+
'Payload': io.BytesIO(bytes(json.dumps({'path': '/mnt/efs/', 'directories': [], 'files': [], 'statusCode': 200}), 'utf-8')),
151+
'ExecutedVersion': 'string'
152+
}
153+
154+
lambda_invoke_make_dir_response = {
155+
'StatusCode': 200,
156+
'FunctionError': 'string',
157+
'LogResult': 'string',
158+
'Payload': io.BytesIO(bytes(json.dumps({"message": "directory creation successful", "statusCode": 200}), 'utf-8')),
159+
'ExecutedVersion': 'string'
160+
}
161+
162+
lambda_invoke_download_response = {
163+
'StatusCode': 200,
164+
'FunctionError': 'string',
165+
'LogResult': 'string',
166+
'Payload': io.BytesIO(bytes(json.dumps({"dzchunkindex": 0, "dztotalchunkcount": 1, "dzchunkbyteoffset": 0,
167+
"chunk_data": "test", "dztotalfilesize": 4}), 'utf-8')),
168+
'ExecutedVersion': 'string'
169+
}
170+
171+
172+
173+
EFS = {'describe_file_systems_no_marker': efs_describe_file_systems_no_marker_response, 'describe_mount_targets': efs_describe_mount_targets_response, 'describe_mount_target_security_groups': efs_describe_mount_target_security_groups_response}
174+
CFN = {'describe_stacks': cfn_describe_stacks_response, 'create_stack': cfn_create_stack_response}
175+
LAMBDA = {'upload': lambda_invoke_upload_response, 'delete': lambda_invoke_delete_response, 'list': lambda_invoke_list_response, 'make_dir': lambda_invoke_make_dir_response, 'download': lambda_invoke_download_response}

0 commit comments

Comments
 (0)