2019-12-27 18:50:03 +00:00
|
|
|
import io
|
|
|
|
import json
|
|
|
|
from datetime import datetime
|
|
|
|
|
2020-01-14 16:45:12 +00:00
|
|
|
from crc import session
|
2020-01-24 16:52:52 +00:00
|
|
|
from crc.models.file import FileModel, FileType, FileModelSchema, FileDataModel
|
|
|
|
from crc.models.workflow import WorkflowSpecModel
|
2020-03-20 12:21:21 +00:00
|
|
|
from crc.services.file_service import FileService
|
2020-03-19 21:13:30 +00:00
|
|
|
from crc.services.workflow_processor import WorkflowProcessor
|
2019-12-27 18:50:03 +00:00
|
|
|
from tests.base_test import BaseTest
|
|
|
|
|
|
|
|
|
2020-02-20 18:30:04 +00:00
|
|
|
class TestFilesApi(BaseTest):
|
2019-12-27 18:50:03 +00:00
|
|
|
|
2020-04-17 17:30:32 +00:00
|
|
|
def minimal_bpmn(self, content):
|
|
|
|
"""Returns a bytesIO object of a well formed BPMN xml file with some string content of your choosing."""
|
|
|
|
minimal_dbpm = "<x><process id='1' isExecutable='false'><startEvent id='a'/></process>%s</x>"
|
|
|
|
return (minimal_dbpm % content).encode()
|
|
|
|
|
2019-12-27 18:50:03 +00:00
|
|
|
def test_list_files_for_workflow_spec(self):
|
|
|
|
self.load_example_data()
|
2020-04-15 14:58:13 +00:00
|
|
|
spec_id = 'core_info'
|
|
|
|
spec = session.query(WorkflowSpecModel).filter_by(id=spec_id).first()
|
|
|
|
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec_id,
|
2019-12-27 18:50:03 +00:00
|
|
|
follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type="application/json", headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual(1, len(json_data))
|
2020-01-14 16:45:12 +00:00
|
|
|
file = FileModelSchema(many=True).load(json_data, session=session)
|
2020-02-28 20:39:44 +00:00
|
|
|
self.assertEqual("%s.bpmn" % spec.name, file[0].name)
|
2019-12-27 18:50:03 +00:00
|
|
|
|
|
|
|
def test_list_multiple_files_for_workflow_spec(self):
|
|
|
|
self.load_example_data()
|
Created a "StudyService" and moved all complex logic around study manipulation out of the study api, and this service, as things were getting complicated. The Workflow Processor no longer creates the WorkflowModel, the study object handles that, and only passes the model into the workflow processor when it is ready to start the workflow.
Created a Study object (seperate from the StudyModel) that can cronstructed on request, and contains a different data structure than we store in the DB. This allows us to return underlying Categories and Workflows in a clean way.
Added a new status to workflows called "not_started", meaning we have not yet instantiated a processor or created a BPMN, they have no version yet and no stored data, just the possiblity of being started.
The Top Level Workflow or "Master" workflow is now a part of the sample data, and loaded at all times.
Removed the ability to "add a workflow to a study" and "remove a workflow from a study", a study contains all possible workflows by definition.
Example data no longer creates users or studies, it just creates the specs.
2020-03-30 12:00:16 +00:00
|
|
|
spec = self.load_test_spec("random_fact")
|
2020-03-04 18:40:25 +00:00
|
|
|
svgFile = FileModel(name="test.svg", type=FileType.svg,
|
2019-12-27 18:50:03 +00:00
|
|
|
primary=False, workflow_spec_id=spec.id)
|
2020-01-14 16:45:12 +00:00
|
|
|
session.add(svgFile)
|
|
|
|
session.flush()
|
2020-02-04 02:56:18 +00:00
|
|
|
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec.id,
|
2019-12-27 18:50:03 +00:00
|
|
|
follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type="application/json", headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual(2, len(json_data))
|
|
|
|
|
|
|
|
def test_create_file(self):
|
|
|
|
self.load_example_data()
|
2020-01-14 16:45:12 +00:00
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
2020-02-04 02:56:18 +00:00
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
|
|
|
rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % spec.id, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
|
|
|
|
self.assert_success(rv)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
2020-01-14 16:45:12 +00:00
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assertEqual(FileType.svg, file.type)
|
|
|
|
self.assertFalse(file.primary)
|
|
|
|
self.assertEqual("image/svg+xml", file.content_type)
|
|
|
|
self.assertEqual(spec.id, file.workflow_spec_id)
|
|
|
|
|
2020-03-24 18:15:21 +00:00
|
|
|
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
2020-01-14 16:45:12 +00:00
|
|
|
file2 = FileModelSchema().load(json_data, session=session)
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assertEqual(file, file2)
|
|
|
|
|
2020-03-19 21:13:30 +00:00
|
|
|
def test_add_file_from_task_and_form_errors_on_invalid_form_field_name(self):
|
|
|
|
self.load_example_data()
|
|
|
|
self.create_reference_document()
|
|
|
|
workflow = self.create_workflow('file_upload_form')
|
|
|
|
processor = WorkflowProcessor(workflow)
|
|
|
|
task = processor.next_task()
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
|
|
|
correct_name = task.task_spec.form.fields[0].id
|
|
|
|
|
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_id=%i&form_field_key=%s' %
|
|
|
|
(workflow.study_id, workflow.id, task.id, "not_a_known_file"), data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-19 21:13:30 +00:00
|
|
|
self.assert_failure(rv, error_code="invalid_form_field_key")
|
|
|
|
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_id=%i&form_field_key=%s' %
|
|
|
|
(workflow.study_id, workflow.id, task.id, correct_name), data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-19 21:13:30 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
|
|
|
|
|
2020-03-13 19:03:57 +00:00
|
|
|
def test_set_reference_file(self):
|
|
|
|
file_name = "irb_document_types.xls"
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), "does_not_matter.xls")}
|
|
|
|
rv = self.app.put('/v1.0/reference_file/%s' % file_name, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-13 19:03:57 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
|
|
|
self.assertEqual(FileType.xls, file.type)
|
|
|
|
self.assertTrue(file.is_reference)
|
|
|
|
self.assertEqual("application/vnd.ms-excel", file.content_type)
|
|
|
|
|
|
|
|
def test_set_reference_file_bad_extension(self):
|
2020-03-20 12:21:21 +00:00
|
|
|
file_name = FileService.IRB_PRO_CATEGORIES_FILE
|
2020-03-13 19:03:57 +00:00
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), "does_not_matter.ppt")}
|
|
|
|
rv = self.app.put('/v1.0/reference_file/%s' % file_name, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-13 19:03:57 +00:00
|
|
|
self.assert_failure(rv, error_code="invalid_file_type")
|
|
|
|
|
|
|
|
def test_get_reference_file(self):
|
|
|
|
file_name = "irb_document_types.xls"
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), "some crazy thing do not care.xls")}
|
|
|
|
rv = self.app.put('/v1.0/reference_file/%s' % file_name, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
rv = self.app.get('/v1.0/reference_file/%s' % file_name, headers=self.logged_in_headers())
|
2020-03-13 19:03:57 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
data_out = rv.get_data()
|
|
|
|
self.assertEqual(b"abcdef", data_out)
|
|
|
|
|
|
|
|
def test_list_reference_files(self):
|
2020-03-20 12:21:21 +00:00
|
|
|
file_name = FileService.IRB_PRO_CATEGORIES_FILE
|
2020-03-13 19:03:57 +00:00
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), file_name)}
|
|
|
|
rv = self.app.put('/v1.0/reference_file/%s' % file_name, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-13 19:03:57 +00:00
|
|
|
|
|
|
|
rv = self.app.get('/v1.0/reference_file',
|
|
|
|
follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type="application/json", headers=self.logged_in_headers())
|
2020-03-13 19:03:57 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual(1, len(json_data))
|
|
|
|
file = FileModelSchema(many=True).load(json_data, session=session)
|
|
|
|
self.assertEqual(file_name, file[0].name)
|
|
|
|
self.assertTrue(file[0].is_reference)
|
|
|
|
|
2020-01-31 16:33:43 +00:00
|
|
|
def test_update_file_info(self):
|
|
|
|
self.load_example_data()
|
|
|
|
file: FileModel = session.query(FileModel).first()
|
|
|
|
file.name = "silly_new_name.bpmn"
|
|
|
|
|
|
|
|
rv = self.app.put('/v1.0/file/%i' % file.id,
|
|
|
|
content_type="application/json",
|
2020-03-24 18:15:21 +00:00
|
|
|
data=json.dumps(FileModelSchema().dump(file)), headers=self.logged_in_headers())
|
2020-01-31 16:33:43 +00:00
|
|
|
self.assert_success(rv)
|
2020-02-04 20:44:06 +00:00
|
|
|
db_file = session.query(FileModel).filter_by(id=file.id).first()
|
2020-01-31 16:33:43 +00:00
|
|
|
self.assertIsNotNone(db_file)
|
|
|
|
self.assertEqual(file.name, db_file.name)
|
|
|
|
|
|
|
|
def test_update_file_data(self):
|
2019-12-27 18:50:03 +00:00
|
|
|
self.load_example_data()
|
2020-01-14 16:45:12 +00:00
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
2019-12-27 18:50:03 +00:00
|
|
|
data = {}
|
2020-04-17 17:30:32 +00:00
|
|
|
data['file'] = io.BytesIO(self.minimal_bpmn("abcdef")), 'my_new_file.bpmn'
|
2020-02-04 20:44:06 +00:00
|
|
|
rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % spec.id, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-02-04 20:44:06 +00:00
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
2019-12-27 18:50:03 +00:00
|
|
|
|
2020-04-17 17:30:32 +00:00
|
|
|
data['file'] = io.BytesIO(self.minimal_bpmn("efghijk")), 'my_new_file.bpmn'
|
2020-01-31 16:33:43 +00:00
|
|
|
rv = self.app.put('/v1.0/file/%i/data' % file.id, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
2020-01-14 16:45:12 +00:00
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
2020-03-04 18:40:25 +00:00
|
|
|
self.assertEqual(2, file.latest_version)
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assertEqual(FileType.bpmn, file.type)
|
|
|
|
self.assertEqual("application/octet-stream", file.content_type)
|
|
|
|
self.assertEqual(spec.id, file.workflow_spec_id)
|
|
|
|
|
2020-03-26 16:51:53 +00:00
|
|
|
# Assure it is updated in the database and properly persisted.
|
|
|
|
file_model = session.query(FileModel).filter(FileModel.id == file.id).first()
|
|
|
|
self.assertEqual(2, file_model.latest_version)
|
|
|
|
|
|
|
|
|
2020-03-24 18:15:21 +00:00
|
|
|
rv = self.app.get('/v1.0/file/%i/data' % file.id, headers=self.logged_in_headers())
|
2020-03-04 18:40:25 +00:00
|
|
|
self.assert_success(rv)
|
|
|
|
data = rv.get_data()
|
|
|
|
self.assertIsNotNone(data)
|
2020-04-17 17:30:32 +00:00
|
|
|
self.assertEqual(self.minimal_bpmn("efghijk"), data)
|
2020-03-04 18:40:25 +00:00
|
|
|
|
|
|
|
def test_update_with_same_exact_data_does_not_increment_version(self):
|
|
|
|
self.load_example_data()
|
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
|
|
|
data = {}
|
2020-04-17 17:30:32 +00:00
|
|
|
data['file'] = io.BytesIO(self.minimal_bpmn("abcdef")), 'my_new_file.bpmn'
|
2020-03-04 18:40:25 +00:00
|
|
|
rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % spec.id, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-04 18:40:25 +00:00
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
|
|
|
self.assertEqual(1, file.latest_version)
|
2020-04-17 17:30:32 +00:00
|
|
|
data['file'] = io.BytesIO(self.minimal_bpmn("abcdef")), 'my_new_file.bpmn'
|
2020-03-04 18:40:25 +00:00
|
|
|
rv = self.app.put('/v1.0/file/%i/data' % file.id, data=data, follow_redirects=True,
|
2020-03-24 18:15:21 +00:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-04 18:40:25 +00:00
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
|
|
|
self.assertEqual(1, file.latest_version)
|
|
|
|
|
2019-12-31 16:31:30 +00:00
|
|
|
|
2019-12-27 18:50:03 +00:00
|
|
|
def test_get_file(self):
|
|
|
|
self.load_example_data()
|
2020-01-14 16:45:12 +00:00
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
|
|
|
file = session.query(FileModel).filter_by(workflow_spec_id=spec.id).first()
|
2020-03-24 18:15:21 +00:00
|
|
|
rv = self.app.get('/v1.0/file/%i/data' % file.id, headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
2020-01-23 20:35:51 +00:00
|
|
|
self.assertEqual("text/xml; charset=utf-8", rv.content_type)
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assertTrue(rv.content_length > 1)
|
|
|
|
|
|
|
|
def test_delete_file(self):
|
|
|
|
self.load_example_data()
|
2020-01-14 16:45:12 +00:00
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
|
|
|
file = session.query(FileModel).filter_by(workflow_spec_id=spec.id).first()
|
2020-03-24 18:15:21 +00:00
|
|
|
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assert_success(rv)
|
2020-03-24 18:15:21 +00:00
|
|
|
rv = self.app.delete('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
|
|
|
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
2019-12-27 18:50:03 +00:00
|
|
|
self.assertEqual(404, rv.status_code)
|
2020-03-26 16:51:53 +00:00
|
|
|
|
2020-04-17 17:30:32 +00:00
|
|
|
def test_change_primary_bpmn(self):
|
|
|
|
self.load_example_data()
|
|
|
|
spec = session.query(WorkflowSpecModel).first()
|
|
|
|
data = {}
|
|
|
|
data['file'] = io.BytesIO(self.minimal_bpmn("abcdef")), 'my_new_file.bpmn'
|
|
|
|
|
|
|
|
# Add a new BPMN file to the specification
|
|
|
|
rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % spec.id, data=data, follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
file = FileModelSchema().load(json_data, session=session)
|
|
|
|
|
|
|
|
# Delete the primary BPMN file for the workflow.
|
|
|
|
orig_model = session.query(FileModel).\
|
|
|
|
filter(FileModel.primary == True).\
|
|
|
|
filter(FileModel.workflow_spec_id == spec.id).first()
|
|
|
|
rv = self.app.delete('/v1.0/file?file_id=%s' % orig_model.id, headers=self.logged_in_headers())
|
|
|
|
|
|
|
|
|
|
|
|
# Set that new file to be the primary BPMN, assure it has a primary_process_id
|
|
|
|
file.primary = True
|
|
|
|
rv = self.app.put('/v1.0/file/%i' % file.id,
|
|
|
|
content_type="application/json",
|
|
|
|
data=json.dumps(FileModelSchema().dump(file)), headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertTrue(json_data['primary'])
|
|
|
|
self.assertIsNotNone(json_data['primary_process_id'])
|
|
|
|
|