feat: build access level labels from their bits and project names
Labels came from a fixed table matched by subset, so Storage (no bits) matched any level the table lacked, and a level was named after the first entry it contained: the server's Brain/MINDS 2.0 folders (0x10000) showed as Storage and 0x10104 as CBS or PW Open. Labels are now composed from the bits. Projects (bit 16 and up) take the names the server reports to their members through users/current/, and otherwise Project<n>; restrict slot 1 is CBS and any other Restrict<n>. A level with no meaning is shown as Unknown (0x...). chacl reads keys built the same way, refuses public and storage before any request, and lists the user's projects with their keys when given an invalid level.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from mdrsclient.api import UsersApi
|
||||
from mdrsclient.models import FolderSimple
|
||||
from mdrsclient.models.folder import FolderAccessLevel
|
||||
from mdrsclient.services import MdrsService
|
||||
|
||||
TIMESTAMP = "2026-01-01T00:00:00+09:00"
|
||||
BM2 = {0: "Brain/MINDS 2.0"}
|
||||
|
||||
|
||||
def make_folder(access_level: int) -> FolderSimple:
|
||||
return FolderSimple(
|
||||
id="f1",
|
||||
pid=None,
|
||||
name="data",
|
||||
access_level=access_level,
|
||||
lock=False,
|
||||
size=0,
|
||||
laboratory_id=1,
|
||||
description="",
|
||||
created_at=TIMESTAMP,
|
||||
updated_at=TIMESTAMP,
|
||||
restrict_opened_at=None,
|
||||
)
|
||||
|
||||
|
||||
def current_user(projects: list[dict]) -> dict:
|
||||
return {
|
||||
"id": 1,
|
||||
"username": "user",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"email": "",
|
||||
"orcid_id": "",
|
||||
"laboratories": [],
|
||||
"is_staff": False,
|
||||
"is_active": True,
|
||||
"is_superuser": False,
|
||||
"is_reviewer": False,
|
||||
"last_login": TIMESTAMP,
|
||||
"date_joined": TIMESTAMP,
|
||||
"projects": projects,
|
||||
}
|
||||
|
||||
|
||||
class TestId2Label(unittest.TestCase):
|
||||
def test_single_levels(self) -> None:
|
||||
cases = {
|
||||
0x0000: "Storage",
|
||||
0x0001: "Private",
|
||||
0x0002: "Public",
|
||||
0x0004: "PW Open",
|
||||
0x0100: "CBS Open",
|
||||
0x0104: "CBS or PW Open",
|
||||
}
|
||||
for id, label in cases.items():
|
||||
with self.subTest(id=hex(id)):
|
||||
self.assertEqual(FolderAccessLevel.id2label(id), label)
|
||||
|
||||
def test_restrict_slot_without_a_name_is_numbered(self) -> None:
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x0200), "Restrict2 Open")
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x8000), "Restrict8 Open")
|
||||
|
||||
def test_project_is_named_from_the_server(self) -> None:
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x10000, BM2), "Brain/MINDS 2.0 Open")
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x10004, BM2), "Brain/MINDS 2.0 or PW Open")
|
||||
|
||||
def test_project_not_named_gets_its_bit_position(self) -> None:
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x10000), "Project0 Open")
|
||||
self.assertEqual(FolderAccessLevel.id2label(0x20000, BM2), "Project1 Open")
|
||||
|
||||
def test_parts_are_restrict_then_project_then_password(self) -> None:
|
||||
self.assertEqual(
|
||||
FolderAccessLevel.id2label(0x30304, BM2), "CBS or Restrict2 or Brain/MINDS 2.0 or Project1 or PW Open"
|
||||
)
|
||||
|
||||
def test_meaningless_levels_are_unlabelled(self) -> None:
|
||||
# Private or Public mixed with another bit, a reserved bit, a negative value.
|
||||
for id in (0x0003, 0x0006, 0x0102, 0x10001, 0x0008, 0x0080, -1):
|
||||
with self.subTest(id=hex(id)):
|
||||
self.assertIsNone(FolderAccessLevel.id2label(id))
|
||||
|
||||
|
||||
class TestKey2Id(unittest.TestCase):
|
||||
def test_keys(self) -> None:
|
||||
cases = {
|
||||
"storage": 0x0000,
|
||||
"private": 0x0001,
|
||||
"public": 0x0002,
|
||||
"pw_open": 0x0004,
|
||||
"cbs_open": 0x0100,
|
||||
"cbs_or_pw_open": 0x0104,
|
||||
"restrict1_open": 0x0100,
|
||||
"restrict2_open": 0x0200,
|
||||
"project0_open": 0x10000,
|
||||
"project1_or_pw_open": 0x20004,
|
||||
}
|
||||
for key, id in cases.items():
|
||||
with self.subTest(key=key):
|
||||
self.assertEqual(FolderAccessLevel.key2id(key), id)
|
||||
|
||||
def test_unknown_keys(self) -> None:
|
||||
for key in ("5kikan_open", "restrict9_open", "restrict0_open", "project01_open", "cbs", "_open", "pw_or_open"):
|
||||
with self.subTest(key=key):
|
||||
self.assertIsNone(FolderAccessLevel.key2id(key))
|
||||
|
||||
|
||||
class TestAccessLevelLabel(unittest.TestCase):
|
||||
def test_known_level(self) -> None:
|
||||
self.assertEqual(make_folder(0x0100).access_level_name, "CBS Open")
|
||||
|
||||
def test_project_names_are_used(self) -> None:
|
||||
self.assertEqual(make_folder(0x10000).access_level_label(BM2), "Brain/MINDS 2.0 Open")
|
||||
self.assertEqual(make_folder(0x10000).access_level_name, "Project0 Open")
|
||||
|
||||
def test_meaningless_level_shows_its_value(self) -> None:
|
||||
self.assertEqual(make_folder(0x0008).access_level_name, "Unknown (0x8)")
|
||||
|
||||
|
||||
class TestProjectNames(unittest.TestCase):
|
||||
def test_anonymous_user_asks_nothing(self) -> None:
|
||||
connection = MagicMock()
|
||||
connection.token = None
|
||||
with patch("mdrsclient.services.UsersApi") as users_api:
|
||||
self.assertEqual(MdrsService(connection).project_names(), {})
|
||||
users_api.assert_not_called()
|
||||
|
||||
def test_names_by_bit_position(self) -> None:
|
||||
connection = MagicMock()
|
||||
connection.get.return_value.json.return_value = current_user(
|
||||
[
|
||||
{"id": 1, "name": "Brain/MINDS 2.0", "bit_position": 0},
|
||||
{"id": 2, "name": "B", "bit_position": 3},
|
||||
{"id": 3, "name": "A", "bit_position": 3},
|
||||
{"id": 4, "name": None, "bit_position": 5},
|
||||
]
|
||||
)
|
||||
with patch.object(UsersApi, "_raise_response_error"):
|
||||
names = UsersApi(connection).project_names()
|
||||
self.assertEqual(names, {0: "Brain/MINDS 2.0", 3: "A, B"})
|
||||
|
||||
def test_server_without_projects(self) -> None:
|
||||
connection = MagicMock()
|
||||
user = current_user([])
|
||||
del user["projects"]
|
||||
connection.get.return_value.json.return_value = user
|
||||
with patch.object(UsersApi, "_raise_response_error"):
|
||||
self.assertEqual(UsersApi(connection).project_names(), {})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user