Skip to content

Commit 5df1ea0

Browse files
authored
Merge pull request #567 from cs7-shrey/gdrive_aws_tests
ADD: Tests for Google Drive and AWS Connection Methods
2 parents 2771400 + fbc4d6b commit 5df1ea0

16 files changed

Lines changed: 548 additions & 15 deletions

.github/workflows/code_test_and_deploy.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ jobs:
6060
# run SSH tests only on Linux because Windows and macOS
6161
# are already run within a virtual container and so cannot
6262
# run Linux containers because nested containerisation is disabled.
63+
- name: Setup Google Drive and AWS Secrets
64+
run: |
65+
echo "GDRIVE_CLIENT_ID=${{secrets.GDRIVE_CLIENT_ID}}" >> $GITHUB_ENV
66+
echo "GDRIVE_CLIENT_SECRET=${{secrets.GDRIVE_CLIENT_SECRET}}" >> $GITHUB_ENV
67+
echo "GDRIVE_ROOT_FOLDER_ID=${{secrets.GDRIVE_ROOT_FOLDER_ID}}" >> $GITHUB_ENV
68+
echo "GDRIVE_CONFIG_TOKEN=${{secrets.GDRIVE_CONFIG_TOKEN}}" >> $GITHUB_ENV
69+
echo "AWS_ACCESS_KEY_ID=${{secrets.AWS_ACCESS_KEY_ID}}" >> $GITHUB_ENV
70+
echo "AWS_REGION=${{secrets.AWS_REGION}}" >> $GITHUB_ENV
71+
echo "AWS_BUCKET_NAME=${{secrets.AWS_BUCKET_NAME}}" >> $GITHUB_ENV
72+
echo "AWS_SECRET_ACCESS_KEY=${{secrets.AWS_SECRET_ACCESS_KEY}}" >> $GITHUB_ENV
6373
- name: Test SSH (Linux only)
6474
if: runner.os == 'Linux'
6575
run: |

datashuttle/tui/screens/setup_gdrive.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ def open_browser_and_setup_gdrive_connection(
191191
asyncio.create_task(
192192
self.setup_gdrive_connection_and_update_ui(
193193
gdrive_client_secret=gdrive_client_secret
194-
)
194+
),
195+
name="setup_gdrive_connection_with_browser_task",
195196
)
196197

197198
def prompt_user_for_config_token(self) -> None:
@@ -227,7 +228,8 @@ def setup_gdrive_connection_using_config_token(
227228
self.setup_gdrive_connection_and_update_ui(
228229
gdrive_client_secret=gdrive_client_secret,
229230
config_token=config_token,
230-
)
231+
),
232+
name="setup_gdrive_connection_without_browser_task",
231233
)
232234

233235
async def setup_gdrive_connection_and_update_ui(

datashuttle/tui/shared/configs_content.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def compose(self) -> ComposeResult:
129129
ClickableInput(
130130
self.parent_class.mainwindow,
131131
placeholder="Google Drive Root Folder ID",
132-
id="configs_gdrive_root_folder_id",
132+
id="configs_gdrive_root_folder_id_input",
133133
),
134134
Label("Client ID (Optional)", id="configs_gdrive_client_id_label"),
135135
ClickableInput(
@@ -301,7 +301,7 @@ def on_mount(self) -> None:
301301
"#configs_central_host_username_input",
302302
"#configs_central_host_id_input",
303303
"#configs_gdrive_client_id_input",
304-
"#configs_gdrive_root_folder_id",
304+
"#configs_gdrive_root_folder_id_input",
305305
]:
306306
self.query_one(id).tooltip = get_tooltip(id)
307307

@@ -721,7 +721,7 @@ def fill_inputs_with_project_configs(self) -> None:
721721
input.value = value
722722

723723
# Google Drive Root Folder ID
724-
input = self.query_one("#configs_gdrive_root_folder_id")
724+
input = self.query_one("#configs_gdrive_root_folder_id_input")
725725
value = (
726726
""
727727
if cfg_to_load["gdrive_root_folder_id"] is None
@@ -897,7 +897,7 @@ def get_datashuttle_inputs_from_widgets(self) -> Dict:
897897

898898
cfg_kwargs["gdrive_root_folder_id"] = (
899899
self.get_config_value_from_input_value(
900-
"#configs_gdrive_root_folder_id"
900+
"#configs_gdrive_root_folder_id_input"
901901
)
902902
)
903903

datashuttle/tui/tooltips.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def get_tooltip(id: str) -> str:
8484
"Can be left empty to use rclone's default client (slower)"
8585
)
8686

87-
elif id == "#configs_gdrive_root_folder_id":
87+
elif id == "#configs_gdrive_root_folder_id_input":
8888
tooltip = (
8989
"The Google Drive root folder ID to use for transfer.\n\n"
9090
"It can be obtained by navigating to the folder in Google Drive and copying the ID from the URL.\n\n"

tests/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import copy
33
import glob
4+
import json
45
import logging
56
import os
67
import pathlib
@@ -414,6 +415,49 @@ def check_config_file(config_path, *kwargs):
414415
assert value == config_yaml[name], f"{name}"
415416

416417

418+
# -----------------------------------------------------------------------------
419+
# Search
420+
# -----------------------------------------------------------------------------
421+
422+
423+
def recursive_search_central(project: DataShuttle):
424+
"""
425+
A convenience function to recursively search a
426+
project for files on remote folders using `rclone
427+
lsjson`
428+
"""
429+
all_filenames: list[str] = []
430+
rclone_recursive_search(
431+
project,
432+
(project.cfg["central_path"] / "rawdata").as_posix(),
433+
all_filenames,
434+
)
435+
436+
return all_filenames
437+
438+
439+
def rclone_recursive_search(
440+
project: DataShuttle, path_: str, all_filenames: list[str]
441+
):
442+
output = rclone.call_rclone(
443+
f"lsjson {project.cfg.get_rclone_config_name()}:{path_}", pipe_std=True
444+
)
445+
446+
files_or_folders = json.loads(output.stdout)
447+
448+
for file_or_folder in files_or_folders:
449+
name = file_or_folder["Name"]
450+
is_dir = file_or_folder.get("IsDir", False)
451+
if is_dir:
452+
rclone_recursive_search(
453+
project,
454+
path_ + "/" + name,
455+
all_filenames,
456+
)
457+
else:
458+
all_filenames.append(path_ + "/" + name)
459+
460+
417461
# -----------------------------------------------------------------------------
418462
# Test Helpers
419463
# -----------------------------------------------------------------------------

tests/tests_transfers/aws/__init__.py

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import copy
2+
import os
3+
4+
from datashuttle import DataShuttle
5+
from datashuttle.utils import aws
6+
7+
8+
def setup_project_for_aws(project: DataShuttle):
9+
aws_bucket_name = os.environ["AWS_BUCKET_NAME"]
10+
project.update_config_file(
11+
connection_method="aws",
12+
central_path=f"{aws_bucket_name}/main/{project.project_name}",
13+
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
14+
aws_region=os.environ["AWS_REGION"],
15+
)
16+
17+
18+
def setup_aws_connection(project: DataShuttle):
19+
"""
20+
Convenience function to set up the AWS connection by
21+
mocking the `aws.get_aws_secret_access_key` function.
22+
"""
23+
original_get_secret = copy.deepcopy(aws.get_aws_secret_access_key)
24+
aws.get_aws_secret_access_key = lambda *args, **kwargs: os.environ[
25+
"AWS_SECRET_ACCESS_KEY"
26+
]
27+
28+
project.setup_aws_connection()
29+
30+
aws.get_aws_secret_access_key = original_get_secret
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import pytest
2+
3+
from datashuttle.utils import rclone
4+
5+
from ..base_transfer import BaseTransfer
6+
from . import aws_test_utils
7+
8+
9+
class TestAwsTransfer(BaseTransfer):
10+
@pytest.fixture(
11+
scope="class",
12+
)
13+
def aws_setup(self, pathtable_and_project):
14+
"""
15+
Setup pathtable and project for AWS transfer tests.
16+
"""
17+
pathtable, project = pathtable_and_project
18+
19+
aws_test_utils.setup_project_for_aws(project)
20+
aws_test_utils.setup_aws_connection(project)
21+
22+
project.upload_rawdata()
23+
24+
yield [pathtable, project]
25+
26+
rclone.call_rclone(
27+
f"purge central_{project.project_name}_aws:{project.cfg['central_path']}"
28+
)
29+
30+
@pytest.mark.parametrize(
31+
"sub_names", [["all"], ["all_non_sub", "sub-002"]]
32+
)
33+
@pytest.mark.parametrize(
34+
"ses_names", [["all"], ["ses-002_random-key"], ["all_non_ses"]]
35+
)
36+
@pytest.mark.parametrize(
37+
"datatype", [["all"], ["anat", "all_non_datatype"]]
38+
)
39+
def test_combinations_aws_transfer(
40+
self,
41+
aws_setup,
42+
sub_names,
43+
ses_names,
44+
datatype,
45+
):
46+
pathtable, project = aws_setup
47+
48+
self.run_and_check_transfers(
49+
project, pathtable, sub_names, ses_names, datatype
50+
)

tests/tests_transfers/base_transfer.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
""" """
22

33
import copy
4+
import shutil
45
from pathlib import Path
56

67
import pandas as pd
78
import pytest
89

10+
from datashuttle.utils import rclone
11+
912
from .. import test_utils
1013
from ..base import BaseTest
1114
from .file_conflicts_pathtable import get_pathtable
@@ -159,3 +162,81 @@ def create_all_pathtable_files(self, pathtable):
159162

160163
def central_from_local(self, path_):
161164
return Path(str(copy.copy(path_)).replace("local", "central"))
165+
166+
# -------------------------------------------------------------------------
167+
# Transfer
168+
# -------------------------------------------------------------------------
169+
170+
def run_and_check_transfers(
171+
self, project, pathtable, sub_names, ses_names, datatype
172+
):
173+
true_central_path = project.cfg["central_path"]
174+
tmp_central_path = (
175+
project.cfg["central_path"] / "tmp" / project.project_name
176+
)
177+
self.remake_logging_path(project)
178+
179+
project.update_config_file(central_path=tmp_central_path)
180+
181+
project.upload_custom(
182+
"rawdata", sub_names, ses_names, datatype, init_log=False
183+
)
184+
185+
expected_transferred_paths = self.get_expected_transferred_paths(
186+
pathtable, sub_names, ses_names, datatype
187+
)
188+
189+
transferred_files = test_utils.recursive_search_central(project)
190+
paths_to_transferred_files = self.remove_path_before_rawdata(
191+
transferred_files
192+
)
193+
194+
assert sorted(paths_to_transferred_files) == sorted(
195+
expected_transferred_paths
196+
)
197+
198+
# Now, move data from the central path where the project is
199+
# setup, to a temp local folder to test download.
200+
true_local_path = project.cfg["local_path"]
201+
tmp_local_path = (
202+
project.cfg["local_path"] / "tmp" / project.project_name
203+
)
204+
tmp_local_path.mkdir(exist_ok=True, parents=True)
205+
206+
project.update_config_file(local_path=tmp_local_path)
207+
project.update_config_file(central_path=true_central_path)
208+
209+
project.download_custom(
210+
"rawdata", sub_names, ses_names, datatype, init_log=False
211+
)
212+
213+
# Find the transferred paths, tidy them up
214+
# and check expected paths were transferred.
215+
all_transferred = list((tmp_local_path / "rawdata").glob("**/*"))
216+
all_transferred = [
217+
path_ for path_ in all_transferred if path_.is_file()
218+
]
219+
220+
paths_to_transferred_files = self.remove_path_before_rawdata(
221+
all_transferred
222+
)
223+
224+
assert sorted(paths_to_transferred_files) == sorted(
225+
expected_transferred_paths
226+
)
227+
228+
rclone.call_rclone(
229+
f"purge {project.cfg.get_rclone_config_name()}:{tmp_central_path.as_posix()}"
230+
)
231+
232+
shutil.rmtree(tmp_local_path)
233+
234+
self.remake_logging_path(project)
235+
project.update_config_file(local_path=true_local_path)
236+
237+
def remake_logging_path(self, project):
238+
"""
239+
Need to do this to compensate for switching
240+
local_path location in the test environment.
241+
"""
242+
project.get_logging_path().mkdir(parents=True, exist_ok=True)

tests/tests_transfers/gdrive/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)