Skip to content

Commit db57454

Browse files
committed
Harden missing workflow handling and add regression tests
1 parent d4d9dff commit db57454

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

server/controller/workflow.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
from model.workflows import *
1+
from model.workflows import WorkFlowModel
22
from flask import request, make_response, Blueprint
33
import defusedxml.ElementTree as ET
44

55
workFlow = Blueprint('workflow', __name__)
66
workFlowModel = WorkFlowModel()
77

88

9+
def isMissingWorkflow(graphml):
10+
if graphml is None:
11+
return True
12+
# Backward-compatible guard for legacy model return type.
13+
return isinstance(graphml, tuple) and len(graphml) > 0 and graphml[0] is False
14+
15+
916
def getLasteshActionHash(root):
1017
xmlns = root.tag[root.tag.index('{')+1:root.tag.rindex('}')]
1118
return root.find(f'{{{xmlns}}}graph')\
@@ -32,7 +39,7 @@ def postWorkflow():
3239
@workFlow.route("/<serverID>")
3340
def getWorkflow(serverID):
3441
graphml = workFlowModel.get(serverID)
35-
if graphml is None:
42+
if isMissingWorkflow(graphml):
3643
return "Not Found", 404
3744
if('X-Latest-Hash' in request.headers):
3845
latestHash = request.headers['X-Latest-Hash']
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import importlib
2+
import pathlib
3+
import sys
4+
import types
5+
import unittest
6+
7+
from flask import Flask
8+
9+
VALID_GRAPHML = (
10+
'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
11+
'<graph edgedefault="directed">'
12+
'<actionHistory><hash>hash-1</hash></actionHistory>'
13+
'</graph>'
14+
'</graphml>'
15+
)
16+
17+
18+
class FakeWorkFlowModel:
19+
def __init__(self, graph_response):
20+
self.graph_response = graph_response
21+
22+
def get(self, _server_id):
23+
return self.graph_response
24+
25+
26+
class WorkflowControllerTests(unittest.TestCase):
27+
@classmethod
28+
def setUpClass(cls):
29+
server_root = pathlib.Path(__file__).resolve().parents[1]
30+
if str(server_root) not in sys.path:
31+
sys.path.insert(0, str(server_root))
32+
33+
fake_model_pkg = types.ModuleType('model')
34+
fake_model_workflows = types.ModuleType('model.workflows')
35+
36+
class StubWorkFlowModel:
37+
def get(self, _server_id):
38+
return None
39+
40+
fake_model_workflows.WorkFlowModel = StubWorkFlowModel
41+
fake_model_pkg.workflows = fake_model_workflows
42+
43+
sys.modules['model'] = fake_model_pkg
44+
sys.modules['model.workflows'] = fake_model_workflows
45+
46+
if 'controller.workflow' in sys.modules:
47+
del sys.modules['controller.workflow']
48+
cls.workflow_module = importlib.import_module('controller.workflow')
49+
50+
def make_client(self, graph_response):
51+
self.workflow_module.workFlowModel = FakeWorkFlowModel(graph_response)
52+
app = Flask(__name__)
53+
app.register_blueprint(self.workflow_module.workFlow, url_prefix='/workflow')
54+
return app.test_client()
55+
56+
def test_missing_workflow_returns_404_for_none(self):
57+
client = self.make_client(None)
58+
response = client.get('/workflow/missing-id')
59+
self.assertEqual(response.status_code, 404)
60+
self.assertEqual(response.get_data(as_text=True), 'Not Found')
61+
62+
def test_missing_workflow_returns_404_for_legacy_tuple(self):
63+
client = self.make_client((False, 'Record Not Found'))
64+
response = client.get('/workflow/missing-id')
65+
self.assertEqual(response.status_code, 404)
66+
self.assertEqual(response.get_data(as_text=True), 'Not Found')
67+
68+
def test_hash_header_returns_400_for_different_history(self):
69+
client = self.make_client(VALID_GRAPHML)
70+
response = client.get('/workflow/existing-id', headers={'X-Latest-Hash': 'unknown-hash'})
71+
self.assertEqual(response.status_code, 400)
72+
self.assertEqual(response.get_data(as_text=True), 'Different History')
73+
74+
def test_hash_header_returns_200_for_matching_history(self):
75+
client = self.make_client(VALID_GRAPHML)
76+
response = client.get('/workflow/existing-id', headers={'X-Latest-Hash': 'hash-1'})
77+
self.assertEqual(response.status_code, 200)
78+
self.assertEqual(response.get_data(as_text=True), VALID_GRAPHML)
79+
80+
81+
if __name__ == '__main__':
82+
unittest.main()

0 commit comments

Comments
 (0)