Files
mdrs-client-python/tests/test_services.py
T
orrisroot 3081d7fbb3 fix: report a locked folder as FolderLockedException
Fetching a locked folder before its password is sent returns 200 with
only its listing fields, without metadata, sub_folders and path, so
FoldersApi.retrieve failed with a pydantic ValidationError. The DOI
route fetches the folder before it can see the lock, so a
password-protected DOI folder could not be opened even with its
password.

- Add FolderLockedException, a subclass of UnauthorizedException, and
  raise it from retrieve on that short record and from the lock checks.
- On a DOI folder, send the password and fetch it again when the first
  fetch finds it locked.
2026-11-13 19:01:56 +09:00

105 lines
3.8 KiB
Python

import unittest
from unittest.mock import MagicMock, patch
from mdrsclient.api import FoldersApi
from mdrsclient.exceptions import FolderLockedException
from mdrsclient.models import Doi, Folder, Laboratories, Laboratory
from mdrsclient.services import MdrsService
TIMESTAMP = "2026-01-01T00:00:00+09:00"
DOI = "10.60178/cbs.20260429-001"
LABORATORY = Laboratory(id=1, name="mylab", pi_name="PI", full_name="My Laboratory")
def make_locked_folder() -> Folder:
return Folder(
id="f1",
pid=None,
name="root",
access_level=4,
lock=True,
size=0,
laboratory_id=1,
description="",
created_at=TIMESTAMP,
updated_at=TIMESTAMP,
restrict_opened_at=None,
metadata=[],
sub_folders=[],
path="/root/",
)
# What the server returns for a locked folder not yet opened with its password.
SHORT_RECORD = {
"id": "f1",
"pid": None,
"name": "root",
"access_level": 4,
"lock": True,
"size": 0,
"laboratory_id": 1,
"description": "",
"created_at": TIMESTAMP,
"updated_at": TIMESTAMP,
"restrict_opened_at": None,
"application": {"id": 0, "status": 0},
}
class TestRetrieveLockedFolder(unittest.TestCase):
def retrieve(self, data: dict) -> Folder:
connection = MagicMock()
connection.get.return_value.json.return_value = data
with patch.object(FoldersApi, "_raise_response_error"):
return FoldersApi(connection).retrieve("f1")
def test_short_record_means_locked(self):
with self.assertRaisesRegex(FolderLockedException, "Folder `root` is locked."):
self.retrieve(SHORT_RECORD)
def test_full_record_of_a_locked_folder_is_read(self):
full = dict(SHORT_RECORD, metadata=[], sub_folders=[], path="/root/")
self.assertEqual(self.retrieve(full).path, "/root/")
class TestFindFolderByDoi(unittest.TestCase):
"""The DOI response carries the folder id as a field, not as a nested object."""
def find(self, retrieved: list, password: str | None) -> tuple[MagicMock, tuple]:
laboratories = Laboratories()
laboratories.append(LABORATORY)
with (
patch("mdrsclient.services.DoiApi") as doi_api,
patch("mdrsclient.services.FoldersApi") as folders_api,
patch("mdrsclient.services.LaboratoriesApi") as laboratories_api,
):
doi_api.return_value.retrieve.return_value = Doi(id="20260429-001", doi=DOI, folder_id="f1")
folders_api.return_value.retrieve.side_effect = retrieved
laboratories_api.return_value.list.return_value = laboratories
return folders_api.return_value, MdrsService(MagicMock()).find_folder_by_doi(DOI, password)
def test_a_locked_doi_folder_is_unlocked_with_its_folder_id(self):
api, (folder, laboratory) = self.find([FolderLockedException("locked"), make_locked_folder()], "secret")
api.auth.assert_called_once_with("f1", "secret")
self.assertEqual(api.retrieve.call_count, 2)
self.assertEqual(folder.id, "f1")
self.assertEqual(laboratory.name, "mylab")
def test_a_locked_doi_folder_without_a_password(self):
with self.assertRaisesRegex(FolderLockedException, f"Folder for DOI `{DOI}` is locked."):
self.find([FolderLockedException("locked")], None)
def test_a_full_record_still_asks_for_the_password(self):
# A locked folder whose record is readable anyway, e.g. its owner's.
api, (folder, _) = self.find([make_locked_folder()], "secret")
api.auth.assert_called_once_with("f1", "secret")
self.assertEqual(api.retrieve.call_count, 1)
with self.assertRaises(FolderLockedException):
self.find([make_locked_folder()], None)
if __name__ == "__main__":
unittest.main()