Skip to content

Commit

Permalink
Issue #78: Add validator command to API (#83)
Browse files Browse the repository at this point in the history
* Add validate only API endpoint
* Add test coverage of validate API
* Make sure worker validator can run with no command set
  • Loading branch information
vkbo authored Sep 1, 2021
1 parent f09d254 commit d41a82c
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 9 deletions.
32 changes: 31 additions & 1 deletion dmci/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ def post_delete(metadata_id=None):
else:
return self._formatMsgReturn(OK_RETURN), 200

@self.route("/v1/validate", methods=["POST"])
def post_validate():
"""Process validate command."""
msg, code = self._validate_method_post(request)
return self._formatMsgReturn(msg), code

return

##
Expand All @@ -96,7 +102,7 @@ def _formatMsgReturn(self, msg):
def _insert_update_method_post(self, cmd, request):
"""Process insert or update command requests."""
if request.content_length > self._conf.max_permitted_size:
return f"File bigger than permitted size: {self._conf.max_permitted_size}", 413
return f"The file is larger than maximum size: {self._conf.max_permitted_size}", 413

data = request.get_data()

Expand Down Expand Up @@ -126,6 +132,30 @@ def _insert_update_method_post(self, cmd, request):
self._handle_persist_file(True, full_path)
return OK_RETURN, 200

def _validate_method_post(self, request):
"""Only run the validator for submitted file."""
if request.content_length > self._conf.max_permitted_size:
return f"The file is larger than maximum size: {self._conf.max_permitted_size}", 413

data = request.get_data()

# Cache the job file
file_uuid = uuid.uuid4()
full_path = os.path.join(self._conf.distributor_cache, f"{file_uuid}.xml")
msg, code = self._persist_file(data, full_path)
if code != 200:
self._handle_persist_file(True, full_path)
return msg, code

# Run the validator
worker = Worker("none", full_path, self._xsd_obj)
valid, msg = worker.validate(data)
self._handle_persist_file(True, full_path)
if valid:
return OK_RETURN, 200
else:
return msg, 400

def _distributor_wrapper(self, worker):
"""Run the distributors and handle and parse the results and
parse and combine any error messages.
Expand Down
4 changes: 2 additions & 2 deletions dmci/api/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def validate(self, data):
# Takes in bytes-object data
# Gives msg when both validating and not validating
if not isinstance(data, bytes):
return False, "input must be bytes type"
return False, "Input must be bytes type"

# Check xml file against XML schema definition
valid = self._xsd_obj.validate(etree.fromstring(data))
Expand Down Expand Up @@ -148,7 +148,7 @@ def distribute(self):
def _check_information_content(self, data):
"""Check the information content in the submitted file."""
if not isinstance(data, bytes):
return False, "input must be bytes type"
return False, "Input must be bytes type"

# Read XML file
xml_doc = etree.fromstring(data)
Expand Down
31 changes: 30 additions & 1 deletion tests/test_api/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ def testApiApp_EndPoints(client):
assert client.get("/").status_code == 404
assert client.get("/v1/").status_code == 404

# Get method is not allowed
assert client.get("/v1/insert").status_code == 405
assert client.get("/v1/update").status_code == 405
assert client.get("/v1/validate").status_code == 405

# Bare delete command is not allowed
assert client.get("/v1/delete").status_code == 404
assert client.post("/v1/delete").status_code == 404

# END Test testApiApp_EndPoints

Expand Down Expand Up @@ -191,6 +193,33 @@ def testApiApp_DeleteRequests(client, monkeypatch):
# END Test testApiApp_DeleteRequests


@pytest.mark.api
def testApiApp_ValidateRequests(client, monkeypatch):
"""Test api validate request."""
assert isinstance(client, flask.testing.FlaskClient)

# Test sending 3MB of data
tooLargeFile = bytes(3000000)
assert client.post("/v1/validate", data=tooLargeFile).status_code == 413

# Fail cahcing the file
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert client.post("/v1/validate", data=MOCK_XML).status_code == 507

# Data is valid
with monkeypatch.context() as mp:
mp.setattr("dmci.api.app.Worker.validate", lambda *a: (True, ""))
assert client.post("/v1/validate", data=MOCK_XML).status_code == 200

# Data is not valid
with monkeypatch.context() as mp:
mp.setattr("dmci.api.app.Worker.validate", lambda *a: (False, ""))
assert client.post("/v1/validate", data=MOCK_XML).status_code == 400

# END Test testApiApp_ValidateRequests


@pytest.mark.api
def testApiApp_PersistFile(tmpDir, monkeypatch):
"""Test the persistent file writer function."""
Expand Down
10 changes: 5 additions & 5 deletions tests/test_api/test_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ def testApiWorker_Validator(monkeypatch, filesDir):
failFile = os.path.join(filesDir, "api", "failing.xml")

xsdObj = lxml.etree.XMLSchema(lxml.etree.parse(xsdFile))
passWorker = Worker("insert", passFile, xsdObj)
failWorker = Worker("insert", failFile, xsdObj)
passWorker = Worker("none", passFile, xsdObj)
failWorker = Worker("none", failFile, xsdObj)

# Invalid data format
passData = readFile(passFile)
assert passWorker.validate(passData) == (False, "input must be bytes type")
assert passWorker.validate(passData) == (False, "Input must be bytes type")

# Valid data format
with monkeypatch.context() as mp:
Expand All @@ -137,11 +137,11 @@ def testApiWorker_Validator(monkeypatch, filesDir):
def testApiWorker_CheckInfoContent(monkeypatch, filesDir):
"""Test _check_information_content."""
passFile = os.path.join(filesDir, "api", "passing.xml")
tstWorker = Worker("insert", passFile, None)
tstWorker = Worker("none", passFile, None)

# Invalid data format
passData = readFile(passFile)
assert tstWorker._check_information_content(passData) == (False, "input must be bytes type")
assert tstWorker._check_information_content(passData) == (False, "Input must be bytes type")

# Valid data format
with monkeypatch.context() as mp:
Expand Down

0 comments on commit d41a82c

Please sign in to comment.