chore(release): bump version to 1.3.18
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
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Mark tests directory as a Python package
|
||||
@@ -0,0 +1,418 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user