From 0189ba0b3608f057a6b3cdfa0436765318ff2f7d Mon Sep 17 00:00:00 2001 From: jtyoung84 <104453205+jtyoung84@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:34:17 -0700 Subject: [PATCH] feat: adds new fields to template (#95) * feat: adds new fields to template * feat: removes project name list * docs: update html and readme * docs: cleans up commented out code * Update src/aind_data_transfer_service/templates/index.html Co-authored-by: Helen Lin <46795546+helen-m-lin@users.noreply.github.com> --- README.md | 7 ++ .../configs/job_configs.py | 13 +++ .../configs/job_upload_template.py | 86 +++++++++++------- src/aind_data_transfer_service/server.py | 33 ++++--- .../templates/index.html | 16 ++-- .../templates/job_status.html | 4 +- tests/resources/job_upload_template.xlsx | Bin 5686 -> 6565 bytes tests/resources/sample.csv | 8 +- tests/resources/sample.xlsx | Bin 9133 -> 5611 bytes tests/resources/sample_alt_modality_case.csv | 8 +- tests/resources/sample_empty_rows.csv | 8 +- tests/resources/sample_empty_rows.xlsx | Bin 10893 -> 5607 bytes tests/resources/sample_invalid_ext.txt | 8 +- tests/resources/sample_malformed.csv | 8 +- tests/resources/sample_malformed.xlsx | Bin 9113 -> 5628 bytes tests/test_configs.py | 16 ++++ tests/test_hpc_models.py | 2 + tests/test_job_upload_template.py | 28 ++++-- tests/test_server.py | 60 +++++++----- 19 files changed, 201 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 213a215..32687bb 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ You can go to http://aind-data-transfer-service to submit a `.csv` or `.xlsx` fi What each column means in the job submission template: +- **processor_full_name**: Name of the person submitting the upload job +- **project_name**: Project name. A full list can be downloaded at [Project Names](http://aind-metadata-service/project_names) +- **process_capsule_id**: Optional Code Ocean capsule or pipeline to run when data is uploaded - **platform**: For a list of platforms click [here](https://github.com/AllenNeuralDynamics/aind-data-schema/blob/main/src/aind_data_schema/models/platforms.py). - **acq_datetime**: The time that the data was acquired - **subject_id**: The unique id of the subject @@ -70,8 +73,12 @@ platform = Platform.BEHAVIOR behavior_config = ModalityConfigs(modality=Modality.BEHAVIOR, source=(source_dir / "Behavior")) behavior_videos_config = ModalityConfigs(modality=Modality.BEHAVIOR_VIDEOS, source=(source_dir / "Behavior videos")) metadata_dir = source_dir / "Config" # This is an optional folder of pre-compiled metadata json files +processor_full_name="Anna Apple" +project_name="Ephys Platform" upload_job_configs = BasicUploadJobConfigs( + processor_full_name=processor_full_name, + project_name=project_name, s3_bucket = s3_bucket, platform = platform, subject_id = subject_id, diff --git a/src/aind_data_transfer_service/configs/job_configs.py b/src/aind_data_transfer_service/configs/job_configs.py index 8c2a91a..72c4ce9 100644 --- a/src/aind_data_transfer_service/configs/job_configs.py +++ b/src/aind_data_transfer_service/configs/job_configs.py @@ -131,6 +131,19 @@ class BasicUploadJobConfigs(BaseSettings): aws_param_store_name: Optional[str] = Field(None) + processor_full_name: str = Field( + ..., + description="Name of person uploading data", + title="Processor Full Name", + ) + project_name: str = Field( + ..., description="Name of project", title="Project Name" + ) + process_capsule_id: Optional[str] = Field( + None, + description="Use custom codeocean capsule or pipeline id", + title="Process Capsule ID", + ) s3_bucket: Optional[str] = Field( None, description="Bucket where data will be uploaded", diff --git a/src/aind_data_transfer_service/configs/job_upload_template.py b/src/aind_data_transfer_service/configs/job_upload_template.py index f49c946..bd8762f 100644 --- a/src/aind_data_transfer_service/configs/job_upload_template.py +++ b/src/aind_data_transfer_service/configs/job_upload_template.py @@ -1,6 +1,7 @@ """Module to configure and create xlsx job upload template""" import datetime from io import BytesIO +from typing import Any, Dict, List from aind_data_schema.models.modalities import Modality from aind_data_schema.models.platforms import Platform @@ -18,6 +19,9 @@ class JobUploadTemplate: NUM_TEMPLATE_ROWS = 20 XLSX_DATETIME_FORMAT = "YYYY-MM-DDTHH:mm:ss" HEADERS = [ + "processor_full_name", + "project_name", + "process_capsule_id", "platform", "acq_datetime", "subject_id", @@ -29,6 +33,9 @@ class JobUploadTemplate: ] SAMPLE_JOBS = [ [ + "Anna Apple", + "Behavior Platform", + "1f999652-00a0-4c4b-99b5-64c2985ad070", Platform.BEHAVIOR.abbreviation, datetime.datetime(2023, 10, 4, 4, 0, 0), "123456", @@ -39,6 +46,9 @@ class JobUploadTemplate: "/allen/aind/stage/fake/dir", ], [ + "John Smith", + "Ophys Platform - SLAP2", + None, Platform.SMARTSPIM.abbreviation, datetime.datetime(2023, 3, 4, 16, 30, 0), "654321", @@ -47,6 +57,9 @@ class JobUploadTemplate: "/allen/aind/stage/fake/dir", ], [ + "Anna Apple", + "Ephys Platform", + None, Platform.ECEPHYS.abbreviation, datetime.datetime(2023, 1, 30, 19, 1, 0), "654321", @@ -57,42 +70,51 @@ class JobUploadTemplate: "/allen/aind/stage/fake/dir", ], ] - VALIDATORS = [ - { - "name": "platform", - "type": "list", - "options": [p().abbreviation for p in Platform._ALL], - "column_indexes": [HEADERS.index("platform")], - }, - { - "name": "modality", - "type": "list", - "options": [m().abbreviation for m in Modality._ALL], - "column_indexes": [ - HEADERS.index("modality0"), - HEADERS.index("modality1"), - ], - }, - { - "name": "datetime", - "type": "date", - "column_indexes": [HEADERS.index("acq_datetime")], - }, - ] - @staticmethod - def create_job_template(): + @property + def validators(self) -> List[Dict[str, Any]]: + """ + Returns + ------- + List[Dict[str, Any]] + A list of validators for fields that require validation. + + """ + return [ + { + "name": "platform", + "type": "list", + "options": [p().abbreviation for p in Platform._ALL], + "column_indexes": [self.HEADERS.index("platform")], + }, + { + "name": "modality", + "type": "list", + "options": [m().abbreviation for m in Modality._ALL], + "column_indexes": [ + self.HEADERS.index("modality0"), + self.HEADERS.index("modality1"), + ], + }, + { + "name": "datetime", + "type": "date", + "column_indexes": [self.HEADERS.index("acq_datetime")], + }, + ] + + @property + def excel_sheet_filestream(self) -> BytesIO: """Create job template as xlsx filestream""" - # job template xl_io = BytesIO() workbook = Workbook() workbook.iso_dates = True worksheet = workbook.active - worksheet.append(JobUploadTemplate.HEADERS) - for job in JobUploadTemplate.SAMPLE_JOBS: + worksheet.append(self.HEADERS) + for job in self.SAMPLE_JOBS: worksheet.append(job) # data validators - for validator in JobUploadTemplate.VALIDATORS: + for validator in self.validators: dv_type = validator["type"] dv_name = validator["name"] dv_params = { @@ -108,19 +130,17 @@ def create_job_template(): dv_params["prompt"] = f"Select a {dv_name} from the dropdown" elif dv_type == "date": dv_params["prompt"] = "Provide a {} using {}".format( - dv_name, JobUploadTemplate.XLSX_DATETIME_FORMAT + dv_name, self.XLSX_DATETIME_FORMAT ) dv = DataValidation(**dv_params) for i in validator["column_indexes"]: col = get_column_letter(i + 1) - col_range = ( - f"{col}2:{col}{JobUploadTemplate.NUM_TEMPLATE_ROWS}" - ) + col_range = f"{col}2:{col}{self.NUM_TEMPLATE_ROWS}" dv.add(col_range) if dv_type != "date": continue for (cell,) in worksheet[col_range]: - cell.number_format = JobUploadTemplate.XLSX_DATETIME_FORMAT + cell.number_format = self.XLSX_DATETIME_FORMAT worksheet.add_data_validation(dv) # formatting bold = Font(bold=True) diff --git a/src/aind_data_transfer_service/server.py b/src/aind_data_transfer_service/server.py index 26c871d..de9a2a3 100644 --- a/src/aind_data_transfer_service/server.py +++ b/src/aind_data_transfer_service/server.py @@ -329,11 +329,28 @@ async def jobs(request: Request): ) -def download_job_template(request: Request): +async def download_job_template(_: Request): """Get job template as xlsx filestream for download""" + + # TODO: Cache list of project names try: - xl_io = JobUploadTemplate.create_job_template() + job_template = JobUploadTemplate() + xl_io = job_template.excel_sheet_filestream + return StreamingResponse( + io.BytesIO(xl_io.getvalue()), + media_type=( + "application/" + "vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ), + headers={ + "Content-Disposition": ( + f"attachment; filename={job_template.FILE_NAME}" + ) + }, + status_code=200, + ) except Exception as e: + logging.error(e) return JSONResponse( content={ "message": "Error creating job template", @@ -341,18 +358,6 @@ def download_job_template(request: Request): }, status_code=500, ) - return StreamingResponse( - io.BytesIO(xl_io.getvalue()), - media_type=( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ), - headers={ - "Content-Disposition": ( - f"attachment; filename={JobUploadTemplate.FILE_NAME}" - ) - }, - status_code=200, - ) routes = [ diff --git a/src/aind_data_transfer_service/templates/index.html b/src/aind_data_transfer_service/templates/index.html index d20a1e4..5fcef8f 100644 --- a/src/aind_data_transfer_service/templates/index.html +++ b/src/aind_data_transfer_service/templates/index.html @@ -47,6 +47,7 @@ Submit Jobs | Job Status | Job Submit Template | + Project Names | Help
@@ -144,7 +145,7 @@

Submit Jobs

let jobsLength = jobs.length; var table = document.createElement('table'), tr, td, row; addTableRow( - [ "s3_bucket", "platform", "subject_id", "acq_datetime", "metadata_dir", "modality", "modality.source" ], + [ "processor_full_name", "project_name", "process_capsule_id", "s3_bucket", "platform", "subject_id", "acq_datetime", "metadata_dir", "modality", "modality.source" ], table, tr, td, true ); for (row = 0; row < jobsLength; row++) { @@ -152,11 +153,14 @@

Submit Jobs

let modalities = job.modalities; let modalitiesLength = modalities.length; addTableRow( - [ { value: job.s3_bucket, rowspan: modalitiesLength }, - { value: job.platform.abbreviation, rowspan: modalitiesLength }, - { value: job.subject_id, rowspan: modalitiesLength }, - { value: job.acq_datetime, rowspan: modalitiesLength }, - { value: job.metadata_dir ?? "", rowspan: modalitiesLength }, + [ { value: job.processor_full_name, rowspan: modalitiesLength }, + { value: job.project_name, rowspan: modalitiesLength }, + { value: job.process_capsule_id ?? "", rowspan: modalitiesLength }, + { value: job.s3_bucket, rowspan: modalitiesLength }, + { value: job.platform.abbreviation, rowspan: modalitiesLength }, + { value: job.subject_id, rowspan: modalitiesLength }, + { value: job.acq_datetime, rowspan: modalitiesLength }, + { value: job.metadata_dir ?? "", rowspan: modalitiesLength }, modalities ? modalities[0].modality.abbreviation : "", modalities ? modalities[0].source : "" ], table, tr, td, false diff --git a/src/aind_data_transfer_service/templates/job_status.html b/src/aind_data_transfer_service/templates/job_status.html index 73bb820..f27726c 100644 --- a/src/aind_data_transfer_service/templates/job_status.html +++ b/src/aind_data_transfer_service/templates/job_status.html @@ -26,7 +26,9 @@

Jobs Submitted: {{num_of_jobs}}

diff --git a/tests/resources/job_upload_template.xlsx b/tests/resources/job_upload_template.xlsx index 5c0a8755150f55f01d14871d4c552ebe7e5dd805..9203d33d24c901742e0eb3353783216a81ee097d 100644 GIT binary patch literal 6565 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 ZC9u?$(GW5s0Dy_O#Ss~N?YEi;_&-O+9#8-P diff --git a/tests/resources/sample.csv b/tests/resources/sample.csv index 370494a..2305c47 100644 --- a/tests/resources/sample.csv +++ b/tests/resources/sample.csv @@ -1,4 +1,4 @@ -modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime -ECEPHYS, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 -BEHAVIOR_VIDEOS, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM -BEHAVIOR_VIDEOS, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +processor_full_name, project_name, process_capsule_id, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime +Anna Apple, Ephys Platform, , ECEPHYS, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 +John Smith, Behavior Platform, 1f999652-00a0-4c4b-99b5-64c2985ad070, BEHAVIOR_VIDEOS, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +Anna Apple, Behavior Platform, , BEHAVIOR_VIDEOS, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM diff --git a/tests/resources/sample.xlsx b/tests/resources/sample.xlsx index 52a0fa1b47aa07443e083ab73f98378af5bb8252..94578d741b0711f91a810eb36137c13683546ced 100644 GIT binary patch literal 5611 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 BcA@|P literal 9133 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 Sb^(AV&|d&lz40l3|M!0Z0MyR_ diff --git a/tests/resources/sample_alt_modality_case.csv b/tests/resources/sample_alt_modality_case.csv index 1ce7623..c58f227 100644 --- a/tests/resources/sample_alt_modality_case.csv +++ b/tests/resources/sample_alt_modality_case.csv @@ -1,4 +1,4 @@ -modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime -ecephys, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 -behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM -behavior-videos, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +processor_full_name, project_name, process_capsule_id, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime +Anna Apple, Ephys Platform, , ecephys, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 +John Smith, Behavior Platform, 1f999652-00a0-4c4b-99b5-64c2985ad070, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +Anna Apple, Behavior Platform, , behavior-videos, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM diff --git a/tests/resources/sample_empty_rows.csv b/tests/resources/sample_empty_rows.csv index 0dadca7..9013962 100644 --- a/tests/resources/sample_empty_rows.csv +++ b/tests/resources/sample_empty_rows.csv @@ -1,7 +1,7 @@ -modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime -ECEPHYS, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 -BEHAVIOR_VIDEOS, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM -BEHAVIOR_VIDEOS, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +processor_full_name, project_name, process_capsule_id, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime +Anna Apple, Ephys Platform, , ECEPHYS, dir/data_set_1, ,, some_bucket, 123454, ecephys, 2020-10-10 14:10:10 +John Smith, Behavior Platform, 1f999652-00a0-4c4b-99b5-64c2985ad070, BEHAVIOR_VIDEOS, dir/data_set_2, MRI, dir/data_set_3, open, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM +Anna Apple, Behavior Platform, , BEHAVIOR_VIDEOS, dir/data_set_2, BEHAVIOR_VIDEOS, dir/data_set_3, scratch, 123456, BEHAVIOR, 10/13/2020 1:10:10 PM ,,,,,, ,,,,,, ,,,,,, diff --git a/tests/resources/sample_empty_rows.xlsx b/tests/resources/sample_empty_rows.xlsx index 8c3c02e15de6a115445ecca335f5f76e73648718..62817e5fcc2b71cad3d66caa5f5dea7f4ec1c9d8 100644 GIT binary patch literal 5607 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%0D!VmC=R3?+!82E~$Fb-eS7)8as*0V@H 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 N;t4`H*!ri|zW}8Yf;j*H literal 9113 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 F{{s@)-iZJJ diff --git a/tests/test_configs.py b/tests/test_configs.py index f11cb56..0bbc4a2 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -27,6 +27,9 @@ class TestJobConfigs(unittest.TestCase): expected_job_configs = [ BasicUploadJobConfigs( aws_param_store_name="/some/param/store", + processor_full_name="Anna Apple", + project_name="Ephys Platform", + process_capsule_id=None, s3_bucket="private", platform=Platform.ECEPHYS, modalities=[ @@ -50,6 +53,9 @@ class TestJobConfigs(unittest.TestCase): ), BasicUploadJobConfigs( aws_param_store_name="/some/param/store", + processor_full_name="John Smith", + project_name="Behavior Platform", + process_capsule_id="1f999652-00a0-4c4b-99b5-64c2985ad070", s3_bucket="open", platform=Platform.BEHAVIOR, modalities=[ @@ -80,6 +86,9 @@ class TestJobConfigs(unittest.TestCase): ), BasicUploadJobConfigs( aws_param_store_name="/some/param/store", + processor_full_name="Anna Apple", + project_name="Behavior Platform", + process_capsule_id=None, s3_bucket="scratch", platform=Platform.BEHAVIOR, modalities=[ @@ -149,6 +158,8 @@ def test_parse_csv_file(self): # not formatted correctly with self.assertRaises(Exception) as e1: BasicUploadJobConfigs( + processor_full_name="Anna Apple", + project_name="Behavior Platform", s3_bucket="", platform=Platform.BEHAVIOR, modalities=[Modality.BEHAVIOR_VIDEOS], @@ -214,6 +225,8 @@ def test_malformed_platform(self): with self.assertRaises(AttributeError) as e: BasicUploadJobConfigs( aws_param_store_name="/some/param/store", + processor_full_name="Anna Apple", + project_name="Behavior Platform", s3_bucket="some_bucket2", platform="MISSING", modalities=[ @@ -308,6 +321,9 @@ def test_from_job_and_server_configs(self): " python -m aind_data_transfer.jobs.basic_job" " --json-args ' " '{"aws_param_store_name":"/some/param/store",' + '"processor_full_name":"Anna Apple",' + '"project_name":"Ephys Platform",' + '"process_capsule_id":null,' '"s3_bucket":"private",' '"platform":{"name":"Electrophysiology platform",' '"abbreviation":"ecephys"},' diff --git a/tests/test_hpc_models.py b/tests/test_hpc_models.py index 1860d54..7f7094d 100644 --- a/tests/test_hpc_models.py +++ b/tests/test_hpc_models.py @@ -133,6 +133,8 @@ class TestHpcJobSubmitSettings(unittest.TestCase): example_config = BasicUploadJobConfigs( aws_param_store_name="/some/param/store", + processor_full_name="John Smith", + project_name="Behavior Platform", s3_bucket="some_bucket", platform=Platform.ECEPHYS, modalities=[ diff --git a/tests/test_job_upload_template.py b/tests/test_job_upload_template.py index 11c52b3..bd6bb72 100644 --- a/tests/test_job_upload_template.py +++ b/tests/test_job_upload_template.py @@ -18,7 +18,8 @@ class TestJobUploadTemplate(unittest.TestCase): """Tests job upload template class""" - def read_xl_helper(self, source, return_validators=False): + @staticmethod + def _read_xl_helper(source, return_validators=False): """Helper function to read xlsx contents and validators""" lines = [] workbook = load_workbook(source, read_only=(not return_validators)) @@ -42,15 +43,26 @@ def read_xl_helper(self, source, return_validators=False): workbook.close() return result + @classmethod + def setUpClass(cls): + """Set up test class""" + expected_lines = cls._read_xl_helper(SAMPLE_JOB_TEMPLATE) + job_template = JobUploadTemplate() + (template_lines, template_validators) = cls._read_xl_helper( + job_template.excel_sheet_filestream, True + ) + cls.job_template = job_template + cls.expected_lines = expected_lines + cls.template_lines = template_lines + cls.template_validators = template_validators + return cls + def test_create_job_template(self): """Tests that xlsx job template is created with correct contents and validators""" - expected_lines = self.read_xl_helper(SAMPLE_JOB_TEMPLATE) - (template_lines, template_validators) = self.read_xl_helper( - JobUploadTemplate.create_job_template(), True - ) - self.assertEqual(expected_lines, template_lines) - for validator in template_validators: + expected_lines = self.expected_lines + self.assertEqual(expected_lines, self.template_lines) + for validator in self.template_validators: validator["column_indexes"] = [] for r in validator["ranges"]: rb = (col, *_) = range_boundaries(r) @@ -60,7 +72,7 @@ def test_create_job_template(self): validator["column_indexes"].append(col - 1) del validator["ranges"] self.assertCountEqual( - JobUploadTemplate.VALIDATORS, template_validators + self.job_template.validators, self.template_validators ) diff --git a/tests/test_server.py b/tests/test_server.py index 6d75725..1b0f30d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -8,10 +8,14 @@ from pathlib import Path, PurePosixPath from unittest.mock import MagicMock, patch +from fastapi.responses import StreamingResponse from fastapi.testclient import TestClient from pydantic import SecretStr from requests import Response +from aind_data_transfer_service.configs.job_upload_template import ( + JobUploadTemplate, +) from aind_data_transfer_service.server import app from tests.test_configs import TestJobConfigs @@ -294,7 +298,10 @@ def test_submit_hpc_jobs( { "hpc_settings": '{"qos":"production", "name": "job1"}', "upload_job_settings": ( - '{"s3_bucket": "private", ' + '{"processor_full_name":"Anna Apple", ' + '"project_name":"Ephys Platform", ' + '"process_capsule_id":null, ' + '"s3_bucket": "private", ' '"platform": {"name": "Behavior platform", ' '"abbreviation": "behavior"}, ' '"modalities": [' @@ -360,7 +367,10 @@ def test_submit_hpc_jobs_open_data( { "hpc_settings": '{"qos":"production", "name": "job1"}', "upload_job_settings": ( - '{"s3_bucket": "open", ' + '{"processor_full_name":"Anna Apple", ' + '"project_name":"Ephys Platform", ' + '"process_capsule_id":null, ' + '"s3_bucket": "open", ' '"platform": {"name": "Behavior platform", ' '"abbreviation": "behavior"}, ' '"modalities": [' @@ -534,44 +544,50 @@ def test_jobs_failure(self, mock_get: MagicMock): self.assertEqual(response.status_code, 200) self.assertIn("Submit Jobs", response.text) - @patch( - "aind_data_transfer_service.configs.job_upload_template" - ".JobUploadTemplate.create_job_template" - ) - def test_download_job_template(self, mock_create_template: MagicMock): + def test_download_job_template(self): """Tests that job template downloads as xlsx file.""" - mock_create_template.return_value = BytesIO(b"mock_template_stream") + with TestClient(app) as client: response = client.get("/api/job_upload_template") - expected_file_name_header = ( - "attachment; filename=job_upload_template.xlsx" + + expected_job_template = JobUploadTemplate() + expected_file_stream = expected_job_template.excel_sheet_filestream + expected_streaming_response = StreamingResponse( + BytesIO(expected_file_stream.getvalue()), + media_type=( + "application/" + "vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ), + headers={ + "Content-Disposition": ( + f"attachment; filename={expected_job_template.FILE_NAME}" + ) + }, + status_code=200, ) - self.assertEqual(1, mock_create_template.call_count) - self.assertEqual(200, response.status_code) + self.assertEqual( - expected_file_name_header, response.headers["Content-Disposition"] + expected_streaming_response.headers.items(), + list(response.headers.items()), ) + self.assertEqual(200, response.status_code) - @patch( - "aind_data_transfer_service.configs.job_upload_template" - ".JobUploadTemplate.create_job_template" - ) + @patch("aind_data_transfer_service.server.JobUploadTemplate") + @patch("logging.error") def test_download_invalid_job_template( - self, mock_create_template: MagicMock + self, mock_log_error: MagicMock, mock_job_template: MagicMock ): """Tests that download invalid job template returns errors.""" - mock_create_template.side_effect = Exception( - "mock invalid job template" - ) + mock_job_template.side_effect = Exception("mock invalid job template") with TestClient(app) as client: response = client.get("/api/job_upload_template") expected_response = { "message": "Error creating job template", "data": {"error": "Exception('mock invalid job template',)"}, } - self.assertEqual(1, mock_create_template.call_count) self.assertEqual(500, response.status_code) self.assertEqual(expected_response, response.json()) + mock_log_error.assert_called_once() if __name__ == "__main__":