)EpPq-wfJjh^@LolMcG!3QKLPZm2DAVG
delta 1857
zcmZvdc{tSj7stOd)&`SphD+BTZkYy!$kO7mmMGa$W1H$4OvXORaHC=n2HD4m>|rp4
zkPM~8B;#R1W4og){Rm@A{YKr}b$`$A{Bh3nInO!g^ZuMaUY~SrnwFToEg!!$006=O
z*DW?xteU_JF1}Y)+-G>nmmmO;;6)#-w>=JfH3;dA#i0*``}>uop2Bo$i{AWZ(_`Y$
zCfixu)-c!5;SrZKnpj1nEDF&q-V)l?F#SpM`U_onMU%9^8y5>jmiOe9K0$O1co@kG
zY=q!^xL~D;)Wigq)sW6ei-+``!K*+GVWAkxk1k-xC_A~;{b8N8(Us+%E+62gTJ?-GQ+w;d)u9H^=UM0!H7P!l6x=1ojpGvig_d5g;Eglju
zY<*6?^&ke@#nIiYJc`$|6OZIJ*gfdMG_pP^NV;;+`J8?hY_+J${6hQb2Z~#Q29&_&
z(TAjV5R8wviuys2si5TcPHtqkokQ~jfGckyvH+<9E=Oo|`A<-hk@S|ctf{whG!4zn
zc55_R$_w#jf0`?`OD~KNc&hF&{O#-LLVxHnw=mkIG%l#OwZ~9RcGI#OMNbwlZ?*8$
z65OzRK|gq^j+H7*+@F~uiwAeBYEgO~-nTm35oMEbyq{K8@QbJ1Y-g0*`Olu@d9;-`
zbt-4bQi8z2s!CEPk6Pj-!i$IVPx5hU$zuptiFGWP|4I&8fo!1$Pw$e`=s4Pb;{7Yy
z85_vDU5`WF2*y_*(|ys=`_t}VTd{;#AJubE(Yt(28lGimI_#tVbX}>a@G5vO^RcuX
z&~DR%PZh+Ol_A6Uwk!rCCC9F7DzXACK3j+M#}ee>3Cr!Fubw>bYt)m#+0C#e&n0fZa@rwRI2oDI-NP|
z3Xk02j5Fu%8$Ayl38?9vP`?@9nL=0hBP_Q4rsg+rDH?T~#fca;KO!_Er9NZ)ZNh3s
zXyIzi+j_I=FTKH~hem(5YWc&N;*OXm8V%;-2N9M{$@qe^llZfgi-RF^&6bfP^eb75
zCPcpG>ZO*zy#FH_iY0sHzjmkCp|
ze7X7#Z8T^OwE;SF`0bIl2!`h6!ju`Xb_1I*$
zxl?@X@RGgbF$WRmgThb|W_h8V2(!8{K%_x;SL37DeYy6;(`CAl#=~*@D(#8RWx67b
zkI>EmtWyEm8YmX?+We8n!{ZPkAp*sJ$TX96^J`nB@rj}L7jhz=C42X#=^W{vSxXh<
z1XAycsPJp0&x!JP?3zv_3wm1DO={8?ZQrR?5DFQSH}>6!u^~0Vh;c2ODX9!)abmcH
z!iUP`$M?q5iUi)=>-n`WQZ{?H>W!xMy+@(tDcXAl-rbTFTk*2h)~|hCF9szfMTY4<
z-|r;fq?3B{W7@K~iiw>6n}tZ$VBADi^IGAgAE
zn1$I6YqF#9Y^`T~2JFg_;te`iomOHp^u7}XYhNo`b@-xkQ8lVBBz{d%pTmBgrKOPQ
zvFN|z^TxO+Pxyn}$QTIL!i*)juZwGRmw@m1VV9jV5Xt)$GY}IQRNLP4Jxcz4rSk)4
zOSWR$d%gO8xkJ8m5Sd`C42g&7o*!3)2XjfC;3Y)dz^`cl#Kn2Ac5-n}n5?H}`}SB(
zcC0rF-=aFC;SMS^^EjC)cJBTcJ@lH{zd-F$=rzx+&w#dpw;@GxlHh!Ym6@a)=zAhh
zYT|n$M%o+%0eZba4r}C@~Av7Z!{8f3gqGPBvHbS~htDE2M*1ovE;u2z-S%Hee>wU9
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..9fd6bf9 100644
--- a/tests/test_job_upload_template.py
+++ b/tests/test_job_upload_template.py
@@ -18,7 +18,31 @@
class TestJobUploadTemplate(unittest.TestCase):
"""Tests job upload template class"""
- def read_xl_helper(self, source, return_validators=False):
+ EXAMPLE_PROJECT_NAMES = [
+ "AIND Viral Genetic Tools",
+ "Behavior Platform",
+ "Brain Computer Interface",
+ "Cell Type LUT",
+ "Cognitive flexibility in patch foraging",
+ "Discovery-Brain Wide Circuit Dynamics",
+ "Discovery-Neuromodulator circuit dynamics during foraging",
+ "Dynamic Routing",
+ "Ephys Platform",
+ "Force Foraging",
+ "Information seeking in partially observable environments",
+ "Learning mFISH/V1omFISH",
+ "MSMA Platform",
+ "Medulla",
+ "Neurobiology of Action",
+ "OpenScope",
+ "Ophys Platform - FP and indicator testing",
+ "Ophys Platform - SLAP2",
+ "Single-neuron computations within brain-wide circuits (SCBC)",
+ "Thalamus in the middle",
+ ]
+
+ @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 +66,28 @@ 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(
+ project_names=cls.EXAMPLE_PROJECT_NAMES
+ )
+ (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 +97,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..59ae086 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,91 @@ 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):
+ @patch("requests.get")
+ def test_download_job_template(self, mock_get: MagicMock):
"""Tests that job template downloads as xlsx file."""
- mock_create_template.return_value = BytesIO(b"mock_template_stream")
+
+ mock_response = Response()
+ mock_response.status_code = 200
+ mock_project_names = ["Ephys Platform", "Behavior Platform"]
+ mock_response._content = json.dumps(
+ {"data": mock_project_names}
+ ).encode("utf-8")
+ mock_get.return_value = mock_response
+
+ # 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(
+ project_names=mock_project_names
)
- self.assertEqual(1, mock_create_template.call_count)
- self.assertEqual(200, response.status_code)
+ 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(
- 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("requests.get")
+ @patch("logging.error")
def test_download_invalid_job_template(
- self, mock_create_template: MagicMock
+ self, mock_log_error: MagicMock, mock_get: MagicMock
):
"""Tests that download invalid job template returns errors."""
- mock_create_template.side_effect = Exception(
- "mock invalid job template"
- )
+ mock_get.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()
+
+ @patch("requests.get")
+ @patch("logging.error")
+ def test_download_job_template_no_project_names(
+ self, mock_log_error: MagicMock, mock_get: MagicMock
+ ):
+ """Tests error is raised if there's an issue getting project names"""
+
+ mock_response = Response()
+ mock_response.status_code = 500
+ mock_project_names = []
+ mock_response._content = json.dumps(
+ {"data": mock_project_names}
+ ).encode("utf-8")
+ mock_get.return_value = mock_response
+
+ with TestClient(app) as client:
+ response = client.get("/api/job_upload_template")
+
+ expected_response_content = {
+ "message": "Error creating job template",
+ "data": {"error": "Exception('Unable to get project names!',)"},
+ }
+ self.assertEqual(
+ expected_response_content,
+ json.loads(response.content.decode("utf-8")),
+ )
+ self.assertEqual(500, response.status_code)
+ mock_log_error.assert_called_once()
if __name__ == "__main__":