"""Folder rules: path normalisation, roles, deletes, moves, browsing.""" import os import pytest from helpers import Store from rigshare_core.workspace import Workspace, norm ADMIN = {"edit": True, "queue": True, "admin": True} EDITOR = {"edit": True, "queue": True, "admin": False} VIEWER = {"edit": False, "queue": False, "admin": False} @pytest.fixture def ws(tmp_path): root = tmp_path / "workflows" root.mkdir() return Workspace(Store(str(tmp_path / "data")), str(root)) def touch(ws, rel): path = os.path.join(ws.root, *rel.split("/")) os.makedirs(os.path.dirname(path), exist_ok=True) open(path, "w").write("{}") @pytest.mark.parametrize("raw,expected", [ ("workflows/a.json", "workflows/a.json"), ("workflows%2Fusers%2Fbob%2Fa.json", "workflows/users/bob/a.json"), ("workflows%252Fusers%252Fbob%252Fa.json", "workflows/users/bob/a.json"), ("workflows/shared/../users/bob/a.json", "workflows/users/bob/a.json"), ("../../etc/passwd", "etc/passwd"), ("workflows\\users\\bob", "workflows/users/bob"), ("", ""), ]) def test_norm(raw, expected): assert norm(raw) == expected def test_areas(): assert Workspace.area("users/bob/x.json") == ("private", "bob") assert Workspace.area("shared/Team/x.json") == ("shared", "shared/Team") assert Workspace.area("x.json") == ("common", None) assert Workspace.workflows_rel("workflows/a/b.json") == "a/b.json" assert Workspace.workflows_rel("subgraphs/x.json") is None def test_private_space(ws): assert ws.role("users/bob/x.json", "user", "bob", VIEWER) == "edit", "own space even for viewer accounts" assert ws.role("users/bob/x.json", "user", "alice", EDITOR) is None assert ws.role("users/bob/x.json", "user", "root", ADMIN) == "edit" assert ws.can_delete("users/bob/x.json", "user", "bob", VIEWER) assert not ws.can_delete("users/bob/x.json", "user", "alice", EDITOR) def test_common_space(ws): assert ws.role("x.json", "user", "alice", EDITOR) == "edit" assert ws.role("x.json", "user", "dave", VIEWER) == "view" assert not ws.can_delete("x.json", "user", "alice", EDITOR), "Common deletes are admin-only" assert ws.can_delete("x.json", "user", "root", ADMIN) def test_shared_folder_access_list(ws): folder = ws.create_folder("Team", "bob") assert ws.role(f"{folder}/a.json", "user", "alice", EDITOR) == "edit", "open by default" ws.set_acl(folder, {"members": {"carol": "view", "erin": "edit"}}) assert ws.role(f"{folder}/a.json", "user", "alice", EDITOR) is None assert ws.role(f"{folder}/a.json", "user", "carol", EDITOR) == "view" assert ws.role(f"{folder}/a.json", "user", "erin", EDITOR) == "edit" assert ws.role(f"{folder}/a.json", "user", "erin", VIEWER) == "view", "a role never exceeds account perms" assert ws.role(f"{folder}/a.json", "user", "bob", EDITOR) == "edit", "owner always" assert ws.can_delete(f"{folder}/a.json", "user", "bob", EDITOR) assert not ws.can_delete(f"{folder}/a.json", "user", "erin", EDITOR) def test_moves(ws): folder = ws.create_folder("Team", "bob") assert ws.can_move("users/bob/a.json", f"{folder}/a.json", "user", "bob", EDITOR) assert ws.can_move(f"{folder}/a.json", f"{folder}/b.json", "user", "alice", EDITOR), "rename in place" assert not ws.can_move(f"{folder}/a.json", "users/alice/a.json", "user", "alice", EDITOR), "only owner moves out" assert not ws.can_move("users/bob/a.json", "users/alice/a.json", "user", "bob", EDITOR) @pytest.mark.parametrize("name", ["", "..", "a/b", "x" * 80, ".hidden"]) def test_bad_folder_names(ws, name): with pytest.raises(ValueError): ws.create_folder(name, "bob") def test_list_dir(ws): folder = ws.create_folder("Team", "bob") touch(ws, "common.json") touch(ws, "users/alice/secret.json") touch(ws, f"{folder}/plan.json") shared = ws.list_dir("@shared", "user", "alice", EDITOR) assert {e["name"] for e in shared["entries"]} == {"Team", "common"} ws.set_acl(folder, {"members": {}}) shared = ws.list_dir("@shared", "user", "alice", EDITOR) assert {e["name"] for e in shared["entries"]} == {"common"} with pytest.raises(PermissionError): ws.list_dir("users/alice", "user", "bob", EDITOR) with pytest.raises(PermissionError): ws.list_dir("users", "user", "bob", EDITOR) mine = ws.list_dir("users/alice", "user", "alice", EDITOR) assert [e["name"] for e in mine["entries"]] == ["secret"] and mine["can_mkdir"] def test_folder_operations(ws): touch(ws, "users/alice/keep.json") sub = ws.make_dir("users/alice", "renders", "user", "alice", EDITOR) assert sub == "users/alice/renders" with pytest.raises(PermissionError): ws.make_dir("users/alice", "x", "user", "bob", EDITOR) with pytest.raises(PermissionError): ws.rename_dir("users/alice", "hack", "user", "alice", EDITOR) assert ws.rename_dir(sub, "finals", "user", "alice", EDITOR) == "users/alice/finals" team = ws.make_dir("@shared", "Team", "user", "bob", EDITOR) assert team == "shared/Team" and ws.folders[team]["owner"] == "bob" assert ws.move_dir("users/alice/finals", team, "user", "alice", EDITOR) == "shared/Team/finals" with pytest.raises(PermissionError): ws.delete_dir(team, "user", "alice", EDITOR) ws.delete_dir("shared/Team/finals", "user", "bob", EDITOR) with pytest.raises(PermissionError): ws.delete_dir("users/alice", "user", "alice", EDITOR) def test_rename_user_moves_private_folder(ws): touch(ws, "users/alice/keep.json") ws.create_folder("Team", "alice") ws.rename_user("alice", "alicia") assert os.path.exists(os.path.join(ws.root, "users", "alicia", "keep.json")) assert ws.folders["shared/Team"]["owner"] == "alicia"