d59a150b4f
Bump the package version to 1.3.18, upgrade dependencies, consolidate module exports, add a unit test suite, and document all changes. - Bump package version to 1.3.18 in pyproject.toml - Upgrade pydantic-settings to 2.14.2 and pyright to 1.1.411 - Consolidate package exports in mdrsclient/__init__.py - Add a comprehensive unit test suite in tests/test_commands.py - Document testing execution and add full history in CHANGELOG.md
419 lines
18 KiB
Python
419 lines
18 KiB
Python
import argparse
|
|
import json
|
|
import unittest
|
|
from io import StringIO
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from mdrsclient.client import MdrsClient
|
|
from mdrsclient.commands import (
|
|
ChaclCommand,
|
|
ConfigCommand,
|
|
CpCommand,
|
|
DownloadCommand,
|
|
FileMetadataCommand,
|
|
LabsCommand,
|
|
LoginCommand,
|
|
LogoutCommand,
|
|
LsCommand,
|
|
MetadataCommand,
|
|
MkdirCommand,
|
|
MvCommand,
|
|
RmCommand,
|
|
UploadCommand,
|
|
VersionCommand,
|
|
WhoamiCommand,
|
|
)
|
|
from mdrsclient.models import Folder, Laboratory
|
|
|
|
|
|
class TestCommands(unittest.TestCase):
|
|
def parse_args(self, cmd_class, args_list):
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers(title="subcommands")
|
|
cmd_class.register(subparsers)
|
|
return parser.parse_args(args_list)
|
|
|
|
def test_version_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.version.return_value = "mdrs 1.3.17"
|
|
|
|
args = self.parse_args(VersionCommand, ["version"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertEqual(fake_out.getvalue().strip(), "mdrs 1.3.17")
|
|
|
|
mock_client_class.assert_called_once_with(None)
|
|
mock_client.version.assert_called_once()
|
|
|
|
def test_login_command_with_args(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
|
|
args = self.parse_args(LoginCommand, ["login", "-u", "myuser", "-p", "mypass", "myremote"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertIn("Login Successful", fake_out.getvalue())
|
|
|
|
mock_client_class.parse_remote_host.assert_called_once_with("myremote")
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.login.assert_called_once_with("myuser", "mypass")
|
|
|
|
def test_login_command_interactive(self):
|
|
with (
|
|
patch("mdrsclient.client.MdrsClient") as mock_client_class,
|
|
patch("builtins.input", return_value="myuser_int") as mock_input,
|
|
patch("getpass.getpass", return_value="mypass_int") as mock_getpass,
|
|
):
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
|
|
args = self.parse_args(LoginCommand, ["login", "myremote"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertIn("Login Successful", fake_out.getvalue())
|
|
|
|
mock_input.assert_called_once_with("Username: ")
|
|
mock_getpass.assert_called_once_with("Password: ")
|
|
mock_client.login.assert_called_once_with("myuser_int", "mypass_int")
|
|
|
|
def test_logout_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
|
|
args = self.parse_args(LogoutCommand, ["logout", "myremote"])
|
|
args.func(args)
|
|
|
|
mock_client_class.parse_remote_host.assert_called_once_with("myremote")
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.logout.assert_called_once()
|
|
|
|
def test_whoami_command_logged_in(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
mock_client.connection.token = None
|
|
|
|
mock_user = MagicMock()
|
|
mock_user.username = "test_user"
|
|
mock_client.whoami.return_value = mock_user
|
|
|
|
args = self.parse_args(WhoamiCommand, ["whoami", "myremote"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertEqual(fake_out.getvalue().strip(), "test_user")
|
|
|
|
mock_client_class.parse_remote_host.assert_called_once_with("myremote")
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.whoami.assert_called_once()
|
|
|
|
def test_whoami_command_anonymous(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
mock_client.connection.token = None
|
|
mock_client.whoami.side_effect = Exception("Not logged in")
|
|
|
|
args = self.parse_args(WhoamiCommand, ["whoami", "myremote"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertEqual(fake_out.getvalue().strip(), "(Anonymous)")
|
|
|
|
def test_labs_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client_class.parse_remote_host.return_value = "myremote"
|
|
|
|
mock_lab = MagicMock()
|
|
mock_lab.id = 1
|
|
mock_lab.name = "lab_name"
|
|
mock_lab.pi_name = "pi_name"
|
|
mock_lab.full_name = "full_name"
|
|
mock_client.get_laboratories.return_value = [mock_lab]
|
|
|
|
args = self.parse_args(LabsCommand, ["labs", "myremote"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
output = fake_out.getvalue()
|
|
self.assertIn("Name", output)
|
|
self.assertIn("lab_name", output)
|
|
self.assertIn("pi_name", output)
|
|
|
|
mock_client_class.parse_remote_host.assert_called_once_with("myremote")
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.get_laboratories.assert_called_once()
|
|
|
|
def test_ls_command_plain(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock(spec=MdrsClient)
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
mock_folder = Folder(
|
|
id="folder_id",
|
|
pid=None,
|
|
name="root",
|
|
access_level=1,
|
|
lock=False,
|
|
size=0,
|
|
laboratory_id=1,
|
|
description="",
|
|
created_at="2026-07-02T00:00:00Z",
|
|
updated_at="2026-07-02T00:00:00Z",
|
|
restrict_opened_at=None,
|
|
metadata=[],
|
|
sub_folders=[],
|
|
path="/root",
|
|
)
|
|
|
|
mock_lab = Laboratory(id=1, name="mylab", pi_name="pi_name", full_name="full_name")
|
|
|
|
mock_client.resolve_folder.return_value = (mock_folder, mock_lab)
|
|
|
|
mock_file = MagicMock()
|
|
mock_file.name = "file.txt"
|
|
mock_file.size = 100
|
|
mock_file.updated_at_name = "2026-07-02"
|
|
mock_client.find_files.return_value = [mock_file]
|
|
|
|
args = self.parse_args(LsCommand, ["ls", "myremote:/mylab/"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
output = fake_out.getvalue()
|
|
self.assertIn("Type", output)
|
|
self.assertIn("file.txt", output)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.resolve_folder.assert_called_once_with("myremote:/mylab/", None)
|
|
mock_client.find_files.assert_called_once_with(mock_folder.id)
|
|
|
|
def test_ls_command_json(self):
|
|
with (
|
|
patch("mdrsclient.client.MdrsClient") as mock_client_class,
|
|
patch("mdrsclient.commands.ls.FoldersApi") as mock_folders_api_class,
|
|
):
|
|
mock_folders_api = MagicMock()
|
|
mock_folders_api_class.return_value = mock_folders_api
|
|
mock_folders_api.metadata.return_value = {"folder_meta": "val"}
|
|
|
|
mock_client = MagicMock(spec=MdrsClient)
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client.connection = MagicMock()
|
|
|
|
mock_folder = Folder(
|
|
id="folder_id",
|
|
pid="parent_id",
|
|
name="root",
|
|
access_level=1,
|
|
lock=False,
|
|
size=0,
|
|
laboratory_id=1,
|
|
description="Root folder",
|
|
created_at="2026-07-02T00:00:00Z",
|
|
updated_at="2026-07-02T00:00:00Z",
|
|
restrict_opened_at=None,
|
|
metadata=[],
|
|
sub_folders=[],
|
|
path="/root",
|
|
)
|
|
|
|
mock_lab = Laboratory(id=1, name="mylab", pi_name="pi_name", full_name="full_name")
|
|
|
|
mock_client.resolve_folder.return_value = (mock_folder, mock_lab)
|
|
mock_client.connection.laboratories.find_by_id.return_value = mock_lab
|
|
|
|
mock_file = MagicMock()
|
|
mock_file.id = "file_id"
|
|
mock_file.name = "file.txt"
|
|
mock_file.type = "text"
|
|
mock_file.size = 100
|
|
mock_file.description = "A file"
|
|
mock_file.metadata = {}
|
|
mock_file.download_url = "download/file"
|
|
mock_file.created_at = "2026-07-02T00:00:00Z"
|
|
mock_file.updated_at = "2026-07-02T00:00:00Z"
|
|
mock_client.find_files.return_value = [mock_file]
|
|
|
|
args = self.parse_args(LsCommand, ["ls", "-J", "myremote:/mylab/"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
output = fake_out.getvalue()
|
|
parsed_json = json.loads(output)
|
|
self.assertEqual(parsed_json["name"], "root")
|
|
self.assertEqual(parsed_json["files"][0]["name"], "file.txt")
|
|
|
|
def test_mkdir_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(MkdirCommand, ["mkdir", "myremote:/mylab/newfolder"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.mkdir.assert_called_once_with("myremote:/mylab/newfolder")
|
|
|
|
def test_rm_command_file(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(RmCommand, ["rm", "myremote:/mylab/file.txt"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.rm.assert_called_once_with("myremote:/mylab/file.txt", False)
|
|
|
|
def test_rm_command_recursive(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(RmCommand, ["rm", "-r", "myremote:/mylab/folder"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.rm.assert_called_once_with("myremote:/mylab/folder", True)
|
|
|
|
def test_cp_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(CpCommand, ["cp", "myremote:/mylab/src", "myremote:/mylab/dest"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.cp.assert_called_once_with("myremote:/mylab/src", "myremote:/mylab/dest", False)
|
|
|
|
def test_mv_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(MvCommand, ["mv", "myremote:/mylab/src", "myremote:/mylab/dest"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.mv.assert_called_once_with("myremote:/mylab/src", "myremote:/mylab/dest")
|
|
|
|
def test_chacl_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(ChaclCommand, ["chacl", "-r", "-p", "secret", "private", "myremote:/mylab/"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.chacl.assert_called_once_with("myremote:/mylab/", 1, True, "secret")
|
|
|
|
def test_metadata_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client.metadata.return_value = {"meta_key": "meta_val"}
|
|
|
|
args = self.parse_args(MetadataCommand, ["metadata", "-p", "secret", "myremote:/mylab/"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
parsed_json = json.loads(fake_out.getvalue())
|
|
self.assertEqual(parsed_json["meta_key"], "meta_val")
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.metadata.assert_called_once_with("myremote:/mylab/", "secret")
|
|
|
|
def test_file_metadata_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
mock_client.file_metadata.return_value = {"file_meta": "val"}
|
|
|
|
args = self.parse_args(FileMetadataCommand, ["file-metadata", "-p", "secret", "myremote:/mylab/file.txt"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
parsed_json = json.loads(fake_out.getvalue())
|
|
self.assertEqual(parsed_json["file_meta"], "val")
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.file_metadata.assert_called_once_with("myremote:/mylab/file.txt", "secret")
|
|
|
|
def test_upload_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(UploadCommand, ["upload", "-r", "-s", "local_file.txt", "myremote:/mylab/"])
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.upload.assert_called_once_with("local_file.txt", "myremote:/mylab/", True, True)
|
|
|
|
def test_download_command(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.from_remote.return_value = mock_client
|
|
|
|
args = self.parse_args(
|
|
DownloadCommand, ["download", "-r", "-s", "-p", "pass", "-e", "ex1", "myremote:/mylab/", "local_dir"]
|
|
)
|
|
args.func(args)
|
|
|
|
mock_client_class.from_remote.assert_called_once_with("myremote")
|
|
mock_client.download.assert_called_once_with("myremote:/mylab/", "local_dir", True, True, "pass", ["ex1"])
|
|
|
|
def test_config_create(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
|
|
args = self.parse_args(ConfigCommand, ["config", "create", "myremote", "http://example.com"])
|
|
args.func(args)
|
|
|
|
mock_client_class.assert_called_once_with(None)
|
|
mock_client.config_create.assert_called_once_with("myremote", "http://example.com")
|
|
|
|
def test_config_update(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
|
|
args = self.parse_args(ConfigCommand, ["config", "update", "myremote", "http://example.com"])
|
|
args.func(args)
|
|
|
|
mock_client_class.assert_called_once_with(None)
|
|
mock_client.config_update.assert_called_once_with("myremote", "http://example.com")
|
|
|
|
def test_config_delete(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
|
|
args = self.parse_args(ConfigCommand, ["config", "delete", "myremote"])
|
|
args.func(args)
|
|
|
|
mock_client_class.assert_called_once_with(None)
|
|
mock_client.config_delete.assert_called_once_with("myremote")
|
|
|
|
def test_config_list(self):
|
|
with patch("mdrsclient.client.MdrsClient") as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.config_list.return_value = [("remote1", "url1"), ("remote2", "url2")]
|
|
|
|
args = self.parse_args(ConfigCommand, ["config", "list"])
|
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
|
args.func(args)
|
|
self.assertEqual(fake_out.getvalue(), "remote1:\turl1\nremote2:\turl2\n")
|
|
|
|
mock_client_class.assert_called_once_with(None)
|
|
mock_client.config_list.assert_called_once()
|