2019-12-27 13:50:03 -05:00
|
|
|
import io
|
|
|
|
import json
|
2021-07-06 13:10:20 -04:00
|
|
|
import os
|
A major refactor of how we search and store files, as there was a lot of confusing bits in here.
From an API point of view you can do the following (and only the following)
/files?workflow_spec_id=x
* You can find all files associated with a workflow_spec_id, and add a file with a workflow_spec_id
/files?workflow_id=x
* You can find all files associated with a workflow_id, and add a file that is directly associated with the workflow
/files?workflow_id=x&form_field_key=y
* You can find all files associated with a form element on a running workflow, and add a new file.
Note: you can add multiple files to the same form_field_key, IF they have different file names. If the same name, the original file is archived,
and the new file takes its place.
The study endpoints always return a list of the file metadata associated with the study. Removed /studies-files, but there is an
endpoint called
/studies/all - that returns all the studies in the system, and does include their files.
On a deeper level:
The File model no longer contains:
- study_id,
- task_id,
- form_field_key
Instead, if the file is associated with workflow - then that is the one way it is connected to the study, and we use this relationship to find files for a study.
A file is never associated with a task_id, as these change when the workflow is reloaded.
The form_field_key must match the irb_doc_code, so when requesting files for a form field, we just look up the irb_doc_code.
2020-05-28 08:27:26 -04:00
|
|
|
|
|
|
|
from tests.base_test import BaseTest
|
2019-12-27 13:50:03 -05:00
|
|
|
|
2021-07-06 13:10:20 -04:00
|
|
|
from crc import session, db, app
|
2022-02-02 12:59:56 -05:00
|
|
|
from crc.models.file import FileModel, FileType, FileModelSchema, FileSchema
|
2022-01-11 15:30:22 -05:00
|
|
|
from crc.services.spec_file_service import SpecFileService
|
2020-03-19 17:13:30 -04:00
|
|
|
from crc.services.workflow_processor import WorkflowProcessor
|
2021-06-08 08:03:14 -04:00
|
|
|
from crc.models.data_store import DataStoreModel
|
2021-07-06 13:10:20 -04:00
|
|
|
from crc.services.document_service import DocumentService
|
2020-05-07 13:57:24 -04:00
|
|
|
from example_data import ExampleDataLoader
|
2022-02-02 12:59:56 -05:00
|
|
|
from crc.services.user_file_service import UserFileService
|
2019-12-27 13:50:03 -05:00
|
|
|
|
2022-01-13 15:24:29 -05:00
|
|
|
from sqlalchemy import column
|
|
|
|
|
2019-12-27 13:50:03 -05:00
|
|
|
|
2020-02-20 13:30:04 -05:00
|
|
|
class TestFilesApi(BaseTest):
|
2019-12-27 13:50:03 -05:00
|
|
|
|
2020-03-19 17:13:30 -04:00
|
|
|
def test_add_file_from_task_and_form_errors_on_invalid_form_field_name(self):
|
|
|
|
self.create_reference_document()
|
|
|
|
workflow = self.create_workflow('file_upload_form')
|
|
|
|
processor = WorkflowProcessor(workflow)
|
2020-07-28 13:33:38 -04:00
|
|
|
processor.do_engine_steps()
|
2020-03-19 17:13:30 -04:00
|
|
|
task = processor.next_task()
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
|
|
|
correct_name = task.task_spec.form.fields[0].id
|
|
|
|
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
2021-08-26 10:43:28 -04:00
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_spec_name=%s&form_field_key=%s' %
|
2021-09-29 14:05:45 -04:00
|
|
|
(workflow.study_id, workflow.id, task.get_name(), correct_name), data=data,
|
|
|
|
follow_redirects=True,
|
2020-03-24 14:15:21 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-19 17:13:30 -04:00
|
|
|
self.assert_success(rv)
|
|
|
|
|
2022-01-11 15:30:22 -05:00
|
|
|
def test_update_reference_file_data(self):
|
2021-07-07 12:54:16 -04:00
|
|
|
file_name = "documents.xlsx"
|
2022-02-02 12:59:56 -05:00
|
|
|
filepath = os.path.join(app.root_path, 'static', 'reference', 'documents.xlsx')
|
2021-07-06 13:10:20 -04:00
|
|
|
with open(filepath, 'rb') as myfile:
|
|
|
|
file_data = myfile.read()
|
|
|
|
data = {'file': (io.BytesIO(file_data), file_name)}
|
2022-01-19 13:47:14 -05:00
|
|
|
rv = self.app.put('/v1.0/reference_file/%s/data' % file_name, data=data, follow_redirects=True,
|
2020-03-24 14:15:21 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-13 15:03:57 -04: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)
|
2021-07-06 13:10:20 -04:00
|
|
|
self.assertEqual(FileType.xlsx, file.type)
|
|
|
|
self.assertEqual("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.content_type)
|
2022-01-11 15:30:22 -05:00
|
|
|
# self.assertEqual('dhf8r', json_data['user_uid'])
|
2020-03-13 15:03:57 -04:00
|
|
|
|
|
|
|
def test_set_reference_file_bad_extension(self):
|
2021-07-06 13:10:20 -04:00
|
|
|
file_name = DocumentService.DOCUMENT_LIST
|
2020-03-13 15:03:57 -04:00
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), "does_not_matter.ppt")}
|
2022-01-19 13:47:14 -05:00
|
|
|
rv = self.app.put('/v1.0/reference_file/%s/data' % file_name, data=data, follow_redirects=True,
|
2020-03-24 14:15:21 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-03-13 15:03:57 -04:00
|
|
|
self.assert_failure(rv, error_code="invalid_file_type")
|
|
|
|
|
2022-01-19 15:17:35 -05:00
|
|
|
def test_get_reference_file_data(self):
|
2022-01-11 15:30:22 -05:00
|
|
|
ExampleDataLoader().load_reference_documents()
|
2020-03-13 15:03:57 -04:00
|
|
|
file_name = "irb_document_types.xls"
|
2022-02-02 12:59:56 -05:00
|
|
|
filepath = os.path.join(app.root_path, 'static', 'reference', 'documents.xlsx')
|
2022-01-19 13:47:14 -05:00
|
|
|
with open(filepath, 'rb') as f_open:
|
|
|
|
file_data = f_open.read()
|
2021-07-06 13:10:20 -04:00
|
|
|
data = {'file': (io.BytesIO(file_data), file_name)}
|
2022-01-19 13:47:14 -05:00
|
|
|
self.app.post('/v1.0/reference_file', data=data, follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
rv = self.app.get('/v1.0/reference_file/%s/data' % file_name, headers=self.logged_in_headers())
|
2020-03-13 15:03:57 -04:00
|
|
|
self.assert_success(rv)
|
|
|
|
data_out = rv.get_data()
|
2021-07-06 13:10:20 -04:00
|
|
|
self.assertEqual(file_data, data_out)
|
2020-03-13 15:03:57 -04:00
|
|
|
|
2022-01-19 15:17:35 -05:00
|
|
|
def test_get_reference_file_info(self):
|
2022-02-09 22:13:02 -05:00
|
|
|
self.create_reference_document()
|
2022-02-02 12:59:56 -05:00
|
|
|
rv = self.app.get('/v1.0/reference_file/documents.xlsx', headers=self.logged_in_headers())
|
2022-01-19 15:17:35 -05:00
|
|
|
self.assert_success(rv)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
|
2022-02-02 12:59:56 -05:00
|
|
|
self.assertEqual("documents.xlsx", json_data['name'])
|
|
|
|
self.assertEqual("xlsx", json_data['type'])
|
2022-01-19 15:17:35 -05:00
|
|
|
|
2021-10-12 14:29:15 -04:00
|
|
|
def test_add_reference_file(self):
|
|
|
|
ExampleDataLoader().load_reference_documents()
|
|
|
|
|
|
|
|
file_name = 'new.xlsx'
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), file_name)}
|
|
|
|
rv = self.app.post('/v1.0/reference_file', data=data,
|
|
|
|
follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
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.xlsx, file.type)
|
|
|
|
|
2022-01-19 13:47:14 -05:00
|
|
|
def test_delete_reference_file(self):
|
|
|
|
ExampleDataLoader().load_reference_documents()
|
2022-02-02 12:59:56 -05:00
|
|
|
name = "documents.xlsx"
|
|
|
|
rv = self.app.get('/v1.0/reference_file/%s' % name, headers=self.logged_in_headers())
|
2022-01-19 13:47:14 -05:00
|
|
|
self.assert_success(rv)
|
2022-02-02 12:59:56 -05:00
|
|
|
self.app.delete('/v1.0/reference_file/%s' % name, headers=self.logged_in_headers())
|
2022-01-19 13:47:14 -05:00
|
|
|
db.session.flush()
|
2022-02-02 12:59:56 -05:00
|
|
|
rv = self.app.get('/v1.0/reference_file/%s' % name, headers=self.logged_in_headers())
|
2022-01-19 15:17:35 -05:00
|
|
|
self.assertEqual(404, rv.status_code)
|
|
|
|
self.assertIsNotNone(rv.get_data())
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
2022-02-02 12:59:56 -05:00
|
|
|
self.assertIn('No reference file found with the name documents.xlsx', json_data['message'])
|
2022-01-19 13:47:14 -05:00
|
|
|
|
2020-03-13 15:03:57 -04:00
|
|
|
def test_list_reference_files(self):
|
2020-05-07 13:57:24 -04:00
|
|
|
ExampleDataLoader.clean_db()
|
2021-07-06 13:10:20 -04:00
|
|
|
file_name = DocumentService.DOCUMENT_LIST
|
2022-02-02 12:59:56 -05:00
|
|
|
filepath = os.path.join(app.root_path, 'static', 'reference', 'documents.xlsx')
|
2021-07-06 13:10:20 -04:00
|
|
|
with open(filepath, 'rb') as myfile:
|
|
|
|
file_data = myfile.read()
|
|
|
|
data = {'file': (io.BytesIO(file_data), file_name)}
|
2022-01-11 15:30:22 -05:00
|
|
|
rv = self.app.post('/v1.0/reference_file', data=data, follow_redirects=True,
|
2020-03-24 14:15:21 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2021-07-06 13:10:20 -04:00
|
|
|
self.assert_success(rv)
|
2020-03-13 15:03:57 -04:00
|
|
|
rv = self.app.get('/v1.0/reference_file',
|
|
|
|
follow_redirects=True,
|
2020-03-24 14:15:21 -04:00
|
|
|
content_type="application/json", headers=self.logged_in_headers())
|
2020-03-13 15:03:57 -04: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)
|
|
|
|
|
2022-02-02 12:59:56 -05:00
|
|
|
def create_user_file(self):
|
|
|
|
self.create_reference_document()
|
|
|
|
workflow = self.create_workflow('file_upload_form')
|
|
|
|
processor = WorkflowProcessor(workflow)
|
|
|
|
processor.do_engine_steps()
|
|
|
|
task = processor.next_task()
|
|
|
|
correct_name = task.task_spec.form.fields[0].id
|
2020-01-31 11:33:43 -05:00
|
|
|
|
2022-02-02 12:59:56 -05:00
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_spec_name=%s&form_field_key=%s' %
|
|
|
|
(workflow.study_id, workflow.id, task.get_name(), correct_name), data=data,
|
|
|
|
follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
2020-01-31 11:33:43 -05:00
|
|
|
self.assert_success(rv)
|
2022-02-02 12:59:56 -05:00
|
|
|
return json.loads(rv.get_data(as_text=True))
|
2020-01-31 11:33:43 -05:00
|
|
|
|
2021-09-29 14:05:45 -04:00
|
|
|
def test_load_valid_url_for_files(self):
|
2022-02-02 12:59:56 -05:00
|
|
|
file = self.create_user_file()
|
|
|
|
rv = self.app.get('/v1.0/file/%i' % file['id'], content_type="application/json", headers=self.logged_in_headers())
|
2021-09-29 14:05:45 -04:00
|
|
|
self.assert_success(rv)
|
|
|
|
file_json = json.loads(rv.get_data(as_text=True))
|
|
|
|
print(file_json)
|
|
|
|
self.assertIsNotNone(file_json['url'])
|
|
|
|
file_data_rv = self.app.get(file_json['url'])
|
|
|
|
self.assert_success(file_data_rv)
|
|
|
|
|
2021-06-08 08:03:14 -04:00
|
|
|
def test_get_file_contains_data_store_elements(self):
|
2022-02-02 12:59:56 -05:00
|
|
|
file = self.create_user_file()
|
|
|
|
ds = DataStoreModel(key="my_key", value="my_value", file_id=file['id'])
|
2021-06-08 08:03:14 -04:00
|
|
|
db.session.add(ds)
|
2022-02-02 12:59:56 -05:00
|
|
|
rv = self.app.get('/v1.0/file/%i' % file['id'], headers=self.logged_in_headers())
|
2021-06-08 08:03:14 -04:00
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual("my_value", json_data['data_store']['my_key'])
|
|
|
|
|
2021-04-23 16:14:23 -04:00
|
|
|
def test_get_files_for_form_field_returns_only_those_files(self):
|
|
|
|
self.create_reference_document()
|
|
|
|
workflow = self.create_workflow('file_upload_form')
|
|
|
|
processor = WorkflowProcessor(workflow)
|
|
|
|
processor.do_engine_steps()
|
|
|
|
task = processor.next_task()
|
|
|
|
correct_name = task.task_spec.form.fields[0].id
|
|
|
|
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
2021-08-26 10:43:28 -04:00
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_spec_name=%s&form_field_key=%s' %
|
2021-09-29 14:05:45 -04:00
|
|
|
(workflow.study_id, workflow.id, task.get_name(), correct_name), data=data,
|
|
|
|
follow_redirects=True,
|
2021-04-23 16:14:23 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
|
|
|
|
# Note: this call can be made WITHOUT the task id.
|
|
|
|
rv = self.app.get('/v1.0/file?study_id=%i&workflow_id=%s&form_field_key=%s' %
|
|
|
|
(workflow.study_id, workflow.id, correct_name), follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual(len(json_data), 1)
|
|
|
|
|
|
|
|
# Add another file for a different document type
|
2022-02-02 12:59:56 -05:00
|
|
|
UserFileService().add_workflow_file(workflow.id, 'Study_App_Doc', task.get_name(), 'otherdoc.docx',
|
|
|
|
'application/xcode', b"asdfasdf")
|
2021-04-23 16:14:23 -04:00
|
|
|
|
2021-08-26 10:43:28 -04:00
|
|
|
# Note: this call can be made WITHOUT the task spec name.
|
2021-04-23 16:14:23 -04:00
|
|
|
rv = self.app.get('/v1.0/file?study_id=%i&workflow_id=%s&form_field_key=%s' %
|
|
|
|
(workflow.study_id, workflow.id, correct_name), follow_redirects=True,
|
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual(len(json_data), 1)
|
|
|
|
|
2021-06-22 14:58:52 -04:00
|
|
|
def test_add_file_returns_document_metadata(self):
|
|
|
|
self.create_reference_document()
|
|
|
|
workflow = self.create_workflow('file_upload_form_single')
|
|
|
|
processor = WorkflowProcessor(workflow)
|
|
|
|
processor.do_engine_steps()
|
|
|
|
task = processor.next_task()
|
|
|
|
correct_name = task.task_spec.form.fields[0].id
|
|
|
|
|
|
|
|
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
2021-08-26 10:43:28 -04:00
|
|
|
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_spec_name=%s&form_field_key=%s' %
|
2021-09-29 14:05:45 -04:00
|
|
|
(workflow.study_id, workflow.id, task.get_name(), correct_name), data=data,
|
|
|
|
follow_redirects=True,
|
2021-06-22 14:58:52 -04:00
|
|
|
content_type='multipart/form-data', headers=self.logged_in_headers())
|
|
|
|
self.assert_success(rv)
|
|
|
|
json_data = json.loads(rv.get_data(as_text=True))
|
|
|
|
self.assertEqual('Ancillary Document', json_data['document']['category1'])
|
2021-08-26 08:51:52 -04:00
|
|
|
self.assertEqual('Study Team', json_data['document']['who_uploads?'])
|
2021-04-23 16:14:23 -04:00
|
|
|
|