Skip to content

Commit 3288711

Browse files
authored
Merge pull request #148 from c-simpson/test_harness
Initial changes to support testing
2 parents 6b07976 + f7beccd commit 3288711

File tree

4 files changed

+123
-46
lines changed

4 files changed

+123
-46
lines changed

src/server/Dockerfile

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ RUN pip install --no-cache-dir -r /requirements.txt
1515
COPY . .
1616

1717
RUN mkdir /app/static \
18-
/app/static/raw_data \
19-
/app/static/raw_data/current \
20-
/app/static/output \
21-
/app/static/output/reports \
22-
/app/static/logs \
23-
/app/static/zipped
18+
/app/static/raw_data \
19+
/app/static/raw_data/current \
20+
/app/static/output \
21+
/app/static/output/reports \
22+
/app/static/logs \
23+
/app/static/zipped
24+
25+
RUN [ ! -f /app/static/logs/last_execution.json ] && printf null > /app/static/logs/last_execution.json
2426

2527
RUN chmod -R 777 /app/static
2628

src/server/api/admin_api.py

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,28 @@
88
from config import engine
99
from flask import send_file, request, redirect, jsonify, current_app, abort
1010
from api.file_uploader import validate_and_arrange_upload
11-
from config import RAW_DATA_PATH, OUTPUT_PATH, CURRENT_SOURCE_FILES_PATH, ZIPPED_FILES, LOGS_PATH
11+
from config import (
12+
RAW_DATA_PATH,
13+
OUTPUT_PATH,
14+
CURRENT_SOURCE_FILES_PATH,
15+
ZIPPED_FILES,
16+
LOGS_PATH,
17+
)
18+
19+
ALLOWED_EXTENSIONS = {"csv", "xlsx"}
1220

13-
ALLOWED_EXTENSIONS = {'csv', 'xlsx'}
1421

1522
def __allowed_file(filename):
16-
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
23+
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
1724

1825

1926
# file upload tutorial
20-
@admin_api.route('/api/file', methods=['POST'])
27+
@admin_api.route("/api/file", methods=["POST"])
2128
def uploadCSV():
22-
if 'file' not in request.files:
29+
if "file" not in request.files:
2330
return redirect(request.url)
2431

25-
for file in request.files.getlist('file'):
32+
for file in request.files.getlist("file"):
2633
if __allowed_file(file.filename):
2734
try:
2835
validate_and_arrange_upload(file, RAW_DATA_PATH)
@@ -31,34 +38,39 @@ def uploadCSV():
3138
finally:
3239
file.close()
3340

34-
return redirect('/')
41+
return redirect("/")
3542

3643

37-
@admin_api.route('/api/files/<destination>', methods=['GET'])
44+
@admin_api.route("/api/files/<destination>", methods=["GET"])
3845
def files(destination):
39-
current_app.logger.info('Start returning zip of all data')
40-
if request.args.get('download_current_btn'):
46+
current_app.logger.info("Start returning zip of all data")
47+
if request.args.get("download_current_btn"):
4148
source = RAW_DATA_PATH + destination
42-
if request.args.get('download_archived_btn'):
49+
if request.args.get("download_archived_btn"):
4350
source = RAW_DATA_PATH
44-
if request.args.get('download_output_btn'):
51+
if request.args.get("download_output_btn"):
4552
source = OUTPUT_PATH
4653

47-
zip_name = destination + '_data_out'
54+
zip_name = destination + "_data_out"
4855

4956
try:
50-
current_app.logger.info(shutil.make_archive(ZIPPED_FILES + zip_name, 'zip', source))
51-
return send_file(ZIPPED_FILES + zip_name + '.zip', as_attachment=True,
52-
attachment_filename=zip_name + '.zip')
57+
current_app.logger.info(
58+
shutil.make_archive(ZIPPED_FILES + zip_name, "zip", source)
59+
)
60+
return send_file(
61+
ZIPPED_FILES + zip_name + ".zip",
62+
as_attachment=True,
63+
attachment_filename=zip_name + ".zip",
64+
)
5365
except Exception as e:
5466
return str(e)
5567

5668

57-
@admin_api.route('/api/listCurrentFiles', methods=['GET'])
69+
@admin_api.route("/api/listCurrentFiles", methods=["GET"])
5870
def listCurrentFiles():
5971
result = None
6072

61-
current_app.logger.info('Start returning file list')
73+
current_app.logger.info("Start returning file list")
6274
file_list_result = os.listdir(CURRENT_SOURCE_FILES_PATH)
6375

6476
if len(file_list_result) > 0:
@@ -67,27 +79,24 @@ def listCurrentFiles():
6779
return jsonify(result)
6880

6981

70-
@admin_api.route('/api/execute', methods=['GET'])
82+
@admin_api.route("/api/execute", methods=["GET"])
7183
def execute():
72-
current_app.logger.info('Execute flow')
84+
current_app.logger.info("Execute flow")
7385
flow_script.start_flow()
7486

7587
current_time = datetime.now().ctime()
7688
statistics = getStatistics()
7789

78-
last_execution_details = {
79-
"executionTime": current_time,
80-
"stats": statistics
81-
}
90+
last_execution_details = {"executionTime": current_time, "stats": statistics}
8291

83-
last_execution_file = open(LOGS_PATH + 'last_execution.json', 'w')
92+
last_execution_file = open(LOGS_PATH + "last_execution.json", "w")
8493
last_execution_file.write(json.dumps(last_execution_details))
8594
last_execution_file.close()
8695

8796
return jsonify(success=True)
8897

8998

90-
'''
99+
"""
91100
@admin_api.route('/api/status', methods=['GET'])
92101
def checkStatus():
93102
with engine.connect() as connection:
@@ -99,33 +108,46 @@ def checkStatus():
99108
for row in query_result:
100109
results = dict(row)
101110
return jsonify(results)
102-
'''
111+
"""
103112

104113

105-
@admin_api.route('/api/statistics', methods=['GET'])
114+
@admin_api.route("/api/statistics", methods=["GET"])
106115
def listStatistics():
107116
try:
108-
last_execution_file = open(LOGS_PATH + 'last_execution.json', 'r')
117+
last_execution_file = open(LOGS_PATH + "last_execution.json", "r")
109118
last_execution_details = json.loads(last_execution_file.read())
110119
last_execution_file.close()
111120

121+
except (FileNotFoundError):
122+
current_app.logger.error("last_execution.json file was missing")
123+
return abort(500)
124+
125+
except (json.JSONDecodeError):
126+
current_app.logger.error(
127+
"last_execution.json could not be decoded - possible corruption"
128+
)
129+
return abort(500)
130+
112131
except Exception as e:
113-
return abort(404)
132+
current_app.logger.error("Failure reading last_execution.json: ", e)
133+
return abort(500)
114134

115135
return jsonify(last_execution_details)
116136

117137

118138
def getStatistics():
119139
with engine.connect() as connection:
120-
query = text("SELECT \
121-
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is null and shelterluvpeople_id is null THEN 1 ELSE 0 END) AS \"Only SalesForce Contacts\", \
122-
SUM(CASE WHEN volgistics_id is not null and shelterluvpeople_id is null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS \"Only Volgistics Contacts\", \
123-
SUM(CASE WHEN shelterluvpeople_id is not null and volgistics_id is null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS \"Only Shelterluv Contacts\", \
124-
SUM(CASE WHEN salesforcecontacts_id is not null and shelterluvpeople_id is not null and volgistics_id is null THEN 1 ELSE 0 END) AS \"Only Salesforcec & Shelterluv\", \
125-
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is not null and shelterluvpeople_id is null THEN 1 ELSE 0 END) AS \"Only Salesforce & Volgistics\", \
126-
SUM(CASE WHEN volgistics_id is not null and shelterluvpeople_id is not null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS \"Only Shelterluv & Volgistics\", \
127-
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is not null and shelterluvpeople_id is not null THEN 1 ELSE 0 END) AS \"Salesforcec & Shelterluv & Volgistics\" \
128-
FROM master")
140+
query = text(
141+
'SELECT \
142+
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is null and shelterluvpeople_id is null THEN 1 ELSE 0 END) AS "Only SalesForce Contacts", \
143+
SUM(CASE WHEN volgistics_id is not null and shelterluvpeople_id is null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS "Only Volgistics Contacts", \
144+
SUM(CASE WHEN shelterluvpeople_id is not null and volgistics_id is null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS "Only Shelterluv Contacts", \
145+
SUM(CASE WHEN salesforcecontacts_id is not null and shelterluvpeople_id is not null and volgistics_id is null THEN 1 ELSE 0 END) AS "Only Salesforcec & Shelterluv", \
146+
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is not null and shelterluvpeople_id is null THEN 1 ELSE 0 END) AS "Only Salesforce & Volgistics", \
147+
SUM(CASE WHEN volgistics_id is not null and shelterluvpeople_id is not null and salesforcecontacts_id is null THEN 1 ELSE 0 END) AS "Only Shelterluv & Volgistics", \
148+
SUM(CASE WHEN salesforcecontacts_id is not null and volgistics_id is not null and shelterluvpeople_id is not null THEN 1 ELSE 0 END) AS "Salesforcec & Shelterluv & Volgistics" \
149+
FROM master'
150+
)
129151
query_result = connection.execute(query)
130152

131153
# Need to iterate over the results proxy

src/server/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ psycopg2-binary==2.8.4
88
python-Levenshtein-wheels
99
xlrd
1010
openpyxl
11-
requests
11+
requests
12+
pytest

src/server/test_api.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest, socket, requests
2+
3+
#
4+
# Run 'pytest' from the command line
5+
#
6+
# Running pytest can result in six different exit codes:
7+
# 0 - All tests were collected and passed successfully
8+
# 1 - Tests were collected and run but some of the tests failed
9+
# 2 - Test execution was interrupted by the user
10+
# 3 - Internal error happened while executing tests
11+
# 4 - pytest command line usage error
12+
# 5 - No tests were collected
13+
#
14+
# These codes are represented by the pytest.ExitCode enum
15+
16+
17+
SERVER_URL = "http://server:5000"
18+
19+
### DNS lookup tests
20+
21+
# Ensure DNS not resolving bad host names
22+
def test_bad_dns():
23+
with pytest.raises(socket.gaierror):
24+
socket.getaddrinfo("bad_server_name_that_should_not_resolve", "5000")
25+
26+
27+
# Do we get IPs for good names?
28+
def test_db_dns():
29+
assert (
30+
len(socket.getaddrinfo("db", "5000")) > 0
31+
) # getaddrinfo works for IPv4 and v6
32+
33+
34+
def test_server_dns():
35+
assert len(socket.getaddrinfo("server", "5000")) > 0
36+
37+
38+
def test_client_dns():
39+
assert len(socket.getaddrinfo("client", "5000")) > 0
40+
41+
42+
# Simple API tests
43+
44+
def test_currentFiles():
45+
response = requests.get(SERVER_URL + "/api/listCurrentFiles")
46+
assert response.status_code == 200
47+
48+
49+
def test_statistics():
50+
response = requests.get(SERVER_URL + "/api/statistics")
51+
assert response.status_code == 200
52+

0 commit comments

Comments
 (0)